LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4122 4596 89.7 %
Date: 2026-06-17 01:50:11 Functions: 141 141 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GeoPackage Translator
       4             :  * Purpose:  Implements GDALGeoPackageDataset class
       5             :  * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
       9             :  * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "ogr_geopackage.h"
      15             : #include "ogr_p.h"
      16             : #include "ogr_swq.h"
      17             : #include "gdal_alg.h"
      18             : #include "gdalwarper.h"
      19             : #include "gdal_utils.h"
      20             : #include "ogrgeopackageutility.h"
      21             : #include "ogrsqliteutility.h"
      22             : #include "ogr_wkb.h"
      23             : #include "vrt/vrtdataset.h"
      24             : 
      25             : #include "tilematrixset.hpp"
      26             : 
      27             : #include <cstdlib>
      28             : 
      29             : #include <algorithm>
      30             : #include <limits>
      31             : #include <sstream>
      32             : 
      33             : #define COMPILATION_ALLOWED
      34             : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
      35             : #include "ogrsqlitesqlfunctionscommon.cpp"
      36             : 
      37             : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
      38             : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
      39             : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
      40             :                                       const GByte *pabyHeader,
      41             :                                       int nHeaderBytes);
      42             : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
      43             : 
      44             : /************************************************************************/
      45             : /*                            Tiling schemes                            */
      46             : /************************************************************************/
      47             : 
      48             : typedef struct
      49             : {
      50             :     const char *pszName;
      51             :     int nEPSGCode;
      52             :     double dfMinX;
      53             :     double dfMaxY;
      54             :     int nTileXCountZoomLevel0;
      55             :     int nTileYCountZoomLevel0;
      56             :     int nTileWidth;
      57             :     int nTileHeight;
      58             :     double dfPixelXSizeZoomLevel0;
      59             :     double dfPixelYSizeZoomLevel0;
      60             : } TilingSchemeDefinition;
      61             : 
      62             : static const TilingSchemeDefinition asTilingSchemes[] = {
      63             :     /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
      64             :        Annex E.3 */
      65             :     {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
      66             :      360.0 / 256},
      67             : 
      68             :     /* See global-mercator at
      69             :        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
      70             :     {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
      71             :      256, 78271.516, 78271.516},
      72             : };
      73             : 
      74             : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
      75             : constexpr int MAX_ZOOM_LEVEL = 30;
      76             : 
      77             : /************************************************************************/
      78             : /*                          GetTilingScheme()                           */
      79             : /************************************************************************/
      80             : 
      81             : static std::unique_ptr<TilingSchemeDefinition>
      82         588 : GetTilingScheme(const char *pszName)
      83             : {
      84         588 :     if (EQUAL(pszName, "CUSTOM"))
      85         460 :         return nullptr;
      86             : 
      87         256 :     for (const auto &tilingScheme : asTilingSchemes)
      88             :     {
      89         195 :         if (EQUAL(pszName, tilingScheme.pszName))
      90             :         {
      91          67 :             return std::make_unique<TilingSchemeDefinition>(tilingScheme);
      92             :         }
      93             :     }
      94             : 
      95          61 :     if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
      96           6 :         pszName = "InspireCRS84Quad";
      97             : 
      98         122 :     auto poTM = gdal::TileMatrixSet::parse(pszName);
      99          61 :     if (poTM == nullptr)
     100           1 :         return nullptr;
     101          60 :     if (!poTM->haveAllLevelsSameTopLeft())
     102             :     {
     103           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     104             :                  "Unsupported tiling scheme: not all zoom levels have same top "
     105             :                  "left corner");
     106           0 :         return nullptr;
     107             :     }
     108          60 :     if (!poTM->haveAllLevelsSameTileSize())
     109             :     {
     110           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     111             :                  "Unsupported tiling scheme: not all zoom levels have same "
     112             :                  "tile size");
     113           0 :         return nullptr;
     114             :     }
     115          60 :     if (!poTM->hasOnlyPowerOfTwoVaryingScales())
     116             :     {
     117           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     118             :                  "Unsupported tiling scheme: resolution of consecutive zoom "
     119             :                  "levels is not always 2");
     120           1 :         return nullptr;
     121             :     }
     122          59 :     if (poTM->hasVariableMatrixWidth())
     123             :     {
     124           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     125             :                  "Unsupported tiling scheme: some levels have variable matrix "
     126             :                  "width");
     127           0 :         return nullptr;
     128             :     }
     129         118 :     auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
     130          59 :     poTilingScheme->pszName = pszName;
     131             : 
     132         118 :     OGRSpatialReference oSRS;
     133          59 :     if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
     134             :     {
     135           0 :         return nullptr;
     136             :     }
     137          59 :     if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
     138             :     {
     139           6 :         poTilingScheme->nEPSGCode = 4326;
     140             :     }
     141             :     else
     142             :     {
     143          53 :         const char *pszAuthName = oSRS.GetAuthorityName();
     144          53 :         const char *pszAuthCode = oSRS.GetAuthorityCode();
     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        1123 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     192             : {
     193        1123 :     CPLAssert(hDB != nullptr);
     194             : 
     195        1123 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     196             :                                                 "PRAGMA user_version = %u",
     197             :                                                 m_nApplicationId,
     198        2246 :                                                 m_nUserVersion));
     199        2246 :     return SQLCommand(hDB, osPragma.c_str());
     200             : }
     201             : 
     202        2969 : bool GDALGeoPackageDataset::CloseDB()
     203             : {
     204        2969 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     205        2969 :     m_pSQLFunctionData = nullptr;
     206        2969 :     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        1001 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     223             :                                      int nEPSGCode)
     224             : {
     225        1001 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     226        1001 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     227        1001 :     CPLPopErrorHandler();
     228        1001 :     CPLErrorReset();
     229        1001 :     return eErr;
     230             : }
     231             : 
     232             : OGRSpatialReferenceRefCountedPtr
     233        1456 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     234             :                                      bool bEmitErrorIfNotFound)
     235             : {
     236        1456 :     const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
     237        1456 :     if (oIter != m_oMapSrsIdToSrs.end())
     238             :     {
     239         103 :         return oIter->second;
     240             :     }
     241             : 
     242        1353 :     if (iSrsId == 0 || iSrsId == -1)
     243             :     {
     244         121 :         auto poSpatialRef = OGRSpatialReferenceRefCountedPtr::makeInstance();
     245         121 :         poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     246             : 
     247             :         // See corresponding tests in GDALGeoPackageDataset::GetSrsId
     248         121 :         if (iSrsId == 0)
     249             :         {
     250          31 :             poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
     251             :                                     "unknown", SRS_WGS84_SEMIMAJOR,
     252             :                                     SRS_WGS84_INVFLATTENING);
     253             :         }
     254          90 :         else if (iSrsId == -1)
     255             :         {
     256          90 :             poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
     257          90 :             poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
     258             :         }
     259             : 
     260         242 :         return m_oMapSrsIdToSrs.insert({iSrsId, std::move(poSpatialRef)})
     261         121 :             .first->second;
     262             :     }
     263             : 
     264        2464 :     CPLString oSQL;
     265        1232 :     oSQL.Printf("SELECT srs_name, definition, organization, "
     266             :                 "organization_coordsys_id%s%s "
     267             :                 "FROM gpkg_spatial_ref_sys WHERE "
     268             :                 "srs_id = %d LIMIT 2",
     269        1232 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     270        1232 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     271             : 
     272        2464 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     273             : 
     274        1232 :     if (!oResult || oResult->RowCount() != 1)
     275             :     {
     276          12 :         if (bFallbackToEPSG)
     277             :         {
     278           7 :             CPLDebug("GPKG",
     279             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     280             :                      iSrsId);
     281           7 :             auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
     282           7 :             if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
     283             :             {
     284           5 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     285           5 :                 return poSRS;
     286             :             }
     287             :         }
     288           5 :         else if (bEmitErrorIfNotFound)
     289             :         {
     290           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     291             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     292             :                      iSrsId);
     293           2 :             m_oMapSrsIdToSrs[iSrsId] = nullptr;
     294             :         }
     295           7 :         return nullptr;
     296             :     }
     297             : 
     298        1220 :     const char *pszName = oResult->GetValue(0, 0);
     299        1220 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     300             :     {
     301         511 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     302         511 :         return nullptr;
     303             :     }
     304         709 :     const char *pszWkt = oResult->GetValue(1, 0);
     305         709 :     if (pszWkt == nullptr)
     306           0 :         return nullptr;
     307         709 :     const char *pszOrganization = oResult->GetValue(2, 0);
     308         709 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     309             :     const char *pszWkt2 =
     310         709 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     311         709 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     312          76 :         pszWkt = pszWkt2;
     313             :     const char *pszCoordinateEpoch =
     314         709 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     315             :     const double dfCoordinateEpoch =
     316         709 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     317             : 
     318        1418 :     auto poSpatialRef = OGRSpatialReferenceRefCountedPtr::makeInstance();
     319         709 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     320             :     // Try to import first from EPSG code, and then from WKT
     321         709 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     322         709 :           EQUAL(pszOrganization, "EPSG") &&
     323         685 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     324           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     325         685 :           GDALGPKGImportFromEPSG(poSpatialRef.get(),
     326             :                                  atoi(pszOrganizationCoordsysID)) ==
     327        1418 :               OGRERR_NONE) &&
     328          24 :         poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
     329             :     {
     330           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     331             :                  "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
     332             :                  pszWkt);
     333           0 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     334           0 :         return nullptr;
     335             :     }
     336             : 
     337         709 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     338         709 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     339        1418 :     return m_oMapSrsIdToSrs.insert({iSrsId, std::move(poSpatialRef)})
     340         709 :         .first->second;
     341             : }
     342             : 
     343         347 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     344             : {
     345         347 :     const char *pszName = oSRS.GetName();
     346         347 :     if (pszName)
     347         347 :         return pszName;
     348             : 
     349             :     // Something odd.  Return empty.
     350           0 :     return "Unnamed SRS";
     351             : }
     352             : 
     353             : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
     354           7 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
     355             :     bool bForceEpoch)
     356             : {
     357           7 :     const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
     358             :     auto oResultTable = SQLQuery(
     359             :         hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
     360          14 :              "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
     361           7 :     if (!oResultTable)
     362           0 :         return false;
     363             : 
     364             :     // Temporary remove foreign key checks
     365             :     const GPKGTemporaryForeignKeyCheckDisabler
     366           7 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
     367             : 
     368           7 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
     369             : 
     370           7 :     if (bRet)
     371             :     {
     372             :         std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
     373             :                           "srs_name TEXT NOT NULL,"
     374             :                           "srs_id INTEGER NOT NULL PRIMARY KEY,"
     375             :                           "organization TEXT NOT NULL,"
     376             :                           "organization_coordsys_id INTEGER NOT NULL,"
     377             :                           "definition TEXT NOT NULL,"
     378             :                           "description TEXT, "
     379           7 :                           "definition_12_063 TEXT NOT NULL");
     380           7 :         if (bAddEpoch)
     381           6 :             osSQL += ", epoch DOUBLE";
     382           7 :         osSQL += ")";
     383           7 :         bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
     384             :     }
     385             : 
     386           7 :     if (bRet)
     387             :     {
     388          32 :         for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
     389             :         {
     390          25 :             const char *pszSrsName = oResultTable->GetValue(0, i);
     391          25 :             const char *pszSrsId = oResultTable->GetValue(1, i);
     392          25 :             const char *pszOrganization = oResultTable->GetValue(2, i);
     393             :             const char *pszOrganizationCoordsysID =
     394          25 :                 oResultTable->GetValue(3, i);
     395          25 :             const char *pszDefinition = oResultTable->GetValue(4, i);
     396             :             if (pszSrsName == nullptr || pszSrsId == nullptr ||
     397             :                 pszOrganization == nullptr ||
     398             :                 pszOrganizationCoordsysID == nullptr)
     399             :             {
     400             :                 // should not happen as there are NOT NULL constraints
     401             :                 // But a database could lack such NOT NULL constraints or have
     402             :                 // large values that would cause a memory allocation failure.
     403             :             }
     404          25 :             const char *pszDescription = oResultTable->GetValue(5, i);
     405             :             char *pszSQL;
     406             : 
     407          50 :             OGRSpatialReference oSRS;
     408          25 :             if (pszOrganization && pszOrganizationCoordsysID &&
     409          25 :                 EQUAL(pszOrganization, "EPSG"))
     410             :             {
     411           9 :                 oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
     412             :             }
     413          34 :             if (!oSRS.IsEmpty() && pszDefinition &&
     414           9 :                 !EQUAL(pszDefinition, "undefined"))
     415             :             {
     416           9 :                 oSRS.SetFromUserInput(pszDefinition);
     417             :             }
     418          25 :             char *pszWKT2 = nullptr;
     419          25 :             if (!oSRS.IsEmpty())
     420             :             {
     421           9 :                 const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
     422             :                                                        nullptr};
     423           9 :                 oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
     424           9 :                 if (pszWKT2 && pszWKT2[0] == '\0')
     425             :                 {
     426           0 :                     CPLFree(pszWKT2);
     427           0 :                     pszWKT2 = nullptr;
     428             :                 }
     429             :             }
     430          25 :             if (pszWKT2 == nullptr)
     431             :             {
     432          16 :                 pszWKT2 = CPLStrdup("undefined");
     433             :             }
     434             : 
     435          25 :             if (pszDescription)
     436             :             {
     437          22 :                 pszSQL = sqlite3_mprintf(
     438             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     439             :                     "organization, organization_coordsys_id, definition, "
     440             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     441             :                     "'%q', '%q', '%q', '%q')",
     442             :                     pszSrsName, pszSrsId, pszOrganization,
     443             :                     pszOrganizationCoordsysID, pszDefinition, pszDescription,
     444             :                     pszWKT2);
     445             :             }
     446             :             else
     447             :             {
     448           3 :                 pszSQL = sqlite3_mprintf(
     449             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     450             :                     "organization, organization_coordsys_id, definition, "
     451             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     452             :                     "'%q', '%q', NULL, '%q')",
     453             :                     pszSrsName, pszSrsId, pszOrganization,
     454             :                     pszOrganizationCoordsysID, pszDefinition, pszWKT2);
     455             :             }
     456             : 
     457          25 :             CPLFree(pszWKT2);
     458          25 :             bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
     459          25 :             sqlite3_free(pszSQL);
     460             :         }
     461             :     }
     462             : 
     463           7 :     if (bRet)
     464             :     {
     465           7 :         bRet =
     466           7 :             SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
     467             :     }
     468           7 :     if (bRet)
     469             :     {
     470           7 :         bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
     471             :                                "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
     472             :     }
     473           7 :     if (bRet)
     474             :     {
     475          14 :         bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
     476           7 :                OGRERR_NONE == SQLCommand(hDB,
     477             :                                          "INSERT INTO gpkg_extensions "
     478             :                                          "(table_name, column_name, "
     479             :                                          "extension_name, definition, scope) "
     480             :                                          "VALUES "
     481             :                                          "('gpkg_spatial_ref_sys', "
     482             :                                          "'definition_12_063', 'gpkg_crs_wkt', "
     483             :                                          "'http://www.geopackage.org/spec120/"
     484             :                                          "#extension_crs_wkt', 'read-write')");
     485             :     }
     486           7 :     if (bRet && bAddEpoch)
     487             :     {
     488           6 :         bRet =
     489             :             OGRERR_NONE ==
     490           6 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     491             :                                 "'gpkg_crs_wkt_1_1' "
     492          12 :                                 "WHERE extension_name = 'gpkg_crs_wkt'") &&
     493             :             OGRERR_NONE ==
     494           6 :                 SQLCommand(
     495             :                     hDB,
     496             :                     "INSERT INTO gpkg_extensions "
     497             :                     "(table_name, column_name, extension_name, definition, "
     498             :                     "scope) "
     499             :                     "VALUES "
     500             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     501             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     502             :                     "'read-write')");
     503             :     }
     504           7 :     if (bRet)
     505             :     {
     506           7 :         SoftCommitTransaction();
     507           7 :         m_bHasDefinition12_063 = true;
     508           7 :         if (bAddEpoch)
     509           6 :             m_bHasEpochColumn = true;
     510             :     }
     511             :     else
     512             :     {
     513           0 :         SoftRollbackTransaction();
     514             :     }
     515             : 
     516           7 :     return bRet;
     517             : }
     518             : 
     519        1100 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     520             : {
     521        1100 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     522        1604 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     523         504 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     524             :     {
     525         598 :         OGRErr err = OGRERR_NONE;
     526         598 :         const int nSRSId = SQLGetInteger(
     527             :             hDB,
     528             :             "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
     529             :             "'Undefined SRS' AND organization = 'GDAL'",
     530             :             &err);
     531         598 :         if (err == OGRERR_NONE)
     532          60 :             return nSRSId;
     533             : 
     534             :         // The below WKT definitions are somehow questionable (using a unknown
     535             :         // unit). For GDAL >= 3.9, they won't be used. They will only be used
     536             :         // for earlier versions.
     537             :         const char *pszSQL;
     538             : #define UNDEFINED_CRS_SRS_ID 99999
     539             :         static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
     540             : #define STRINGIFY(x) #x
     541             : #define XSTRINGIFY(x) STRINGIFY(x)
     542         538 :         if (m_bHasDefinition12_063)
     543             :         {
     544             :             /* clang-format off */
     545           1 :             pszSQL =
     546             :                 "INSERT INTO gpkg_spatial_ref_sys "
     547             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     548             :                 "definition, definition_12_063, description) VALUES "
     549             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     550             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     551             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     552             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     553             :                 "AXIS[\"Northing\",NORTH]]',"
     554             :                 "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
     555             :                 "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
     556             :                 "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
     557             :                 "'Custom undefined coordinate reference system')";
     558             :             /* clang-format on */
     559             :         }
     560             :         else
     561             :         {
     562             :             /* clang-format off */
     563         537 :             pszSQL =
     564             :                 "INSERT INTO gpkg_spatial_ref_sys "
     565             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     566             :                 "definition, description) VALUES "
     567             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     568             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     569             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     570             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     571             :                 "AXIS[\"Northing\",NORTH]]',"
     572             :                 "'Custom undefined coordinate reference system')";
     573             :             /* clang-format on */
     574             :         }
     575         538 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     576         538 :             return UNDEFINED_CRS_SRS_ID;
     577             : #undef UNDEFINED_CRS_SRS_ID
     578             : #undef XSTRINGIFY
     579             : #undef STRINGIFY
     580           0 :         return -1;
     581             :     }
     582             : 
     583        1004 :     auto poSRS = OGRSpatialReferenceRefCountedPtr::makeClone(poSRSIn);
     584         502 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     585             :     {
     586             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     587         173 :         if (pszName != nullptr && strlen(pszName) > 0)
     588             :         {
     589         173 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     590           2 :                 return 0;
     591             : 
     592         171 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     593           1 :                 return -1;
     594             :         }
     595             :     }
     596             : 
     597         499 :     const char *pszAuthorityName = poSRS->GetAuthorityName();
     598             : 
     599         499 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     600             :     {
     601             :         // Try to force identify an EPSG code.
     602          28 :         poSRS->AutoIdentifyEPSG();
     603             : 
     604          28 :         pszAuthorityName = poSRS->GetAuthorityName();
     605          28 :         if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
     606             :         {
     607           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode();
     608           0 :             if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
     609             :             {
     610             :                 /* Import 'clean' SRS */
     611           0 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
     612             : 
     613           0 :                 pszAuthorityName = poSRS->GetAuthorityName();
     614             :             }
     615             :         }
     616             : 
     617          28 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     618             :     }
     619             : 
     620             :     // Check whether the EPSG authority code is already mapped to a
     621             :     // SRS ID.
     622         499 :     char *pszSQL = nullptr;
     623         499 :     int nSRSId = DEFAULT_SRID;
     624         499 :     int nAuthorityCode = 0;
     625         499 :     OGRErr err = OGRERR_NONE;
     626         499 :     bool bCanUseAuthorityCode = false;
     627         499 :     const char *const apszIsSameOptions[] = {
     628             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     629             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     630         499 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     631             :     {
     632         471 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode();
     633         471 :         if (pszAuthorityCode)
     634             :         {
     635         471 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     636             :             {
     637         471 :                 nAuthorityCode = atoi(pszAuthorityCode);
     638             :             }
     639             :             else
     640             :             {
     641           0 :                 CPLDebug("GPKG",
     642             :                          "SRS has %s:%s identification, but the code not "
     643             :                          "being an integer value cannot be stored as such "
     644             :                          "in the database.",
     645             :                          pszAuthorityName, pszAuthorityCode);
     646           0 :                 pszAuthorityName = nullptr;
     647             :             }
     648             :         }
     649             :     }
     650             : 
     651         970 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     652         471 :         poSRSIn->GetCoordinateEpoch() == 0)
     653             :     {
     654             :         pszSQL =
     655         466 :             sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     656             :                             "upper(organization) = upper('%q') AND "
     657             :                             "organization_coordsys_id = %d",
     658             :                             pszAuthorityName, nAuthorityCode);
     659             : 
     660         466 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     661         466 :         sqlite3_free(pszSQL);
     662             : 
     663             :         // Got a match? Return it!
     664         466 :         if (OGRERR_NONE == err)
     665             :         {
     666         148 :             auto poRefSRS = GetSpatialRef(nSRSId);
     667             :             bool bOK =
     668         148 :                 (poRefSRS == nullptr ||
     669         149 :                  poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
     670           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     671         148 :             if (bOK)
     672             :             {
     673         147 :                 return nSRSId;
     674             :             }
     675             :             else
     676             :             {
     677           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     678             :                          "Passed SRS uses %s:%d identification, but its "
     679             :                          "definition is not compatible with the "
     680             :                          "definition of that object already in the database. "
     681             :                          "Registering it as a new entry into the database.",
     682             :                          pszAuthorityName, nAuthorityCode);
     683           1 :                 pszAuthorityName = nullptr;
     684           1 :                 nAuthorityCode = 0;
     685             :             }
     686             :         }
     687             :     }
     688             : 
     689             :     // Translate SRS to WKT.
     690         352 :     CPLCharUniquePtr pszWKT1;
     691         352 :     CPLCharUniquePtr pszWKT2_2015;
     692         352 :     CPLCharUniquePtr pszWKT2_2019;
     693         352 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     694         352 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     695         352 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     696             : 
     697         704 :     std::string osEpochTest;
     698         352 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     699             :     {
     700             :         osEpochTest =
     701           3 :             CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
     702             :     }
     703             : 
     704         695 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3) &&
     705         343 :         !poSRS->IsDerivedGeographic())
     706             :     {
     707         686 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     708         343 :         char *pszTmp = nullptr;
     709         343 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     710         343 :         pszWKT1.reset(pszTmp);
     711         343 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     712             :         {
     713           0 :             pszWKT1.reset();
     714             :         }
     715             :     }
     716             :     {
     717         704 :         CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     718         352 :         char *pszTmp = nullptr;
     719         352 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     720         352 :         pszWKT2_2015.reset(pszTmp);
     721         352 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     722             :         {
     723           0 :             pszWKT2_2015.reset();
     724             :         }
     725             :     }
     726             :     {
     727         352 :         char *pszTmp = nullptr;
     728         352 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     729         352 :         pszWKT2_2019.reset(pszTmp);
     730         352 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     731             :         {
     732           0 :             pszWKT2_2019.reset();
     733             :         }
     734             :     }
     735             : 
     736         352 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     737             :     {
     738           0 :         return DEFAULT_SRID;
     739             :     }
     740             : 
     741         352 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     742             :     {
     743             :         // Search if there is already an existing entry with this WKT
     744         349 :         if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
     745             :         {
     746          42 :             if (pszWKT1)
     747             :             {
     748         144 :                 pszSQL = sqlite3_mprintf(
     749             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     750             :                     "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
     751             :                     pszWKT1.get(),
     752          72 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     753          72 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     754             :                     osEpochTest.c_str());
     755             :             }
     756             :             else
     757             :             {
     758          24 :                 pszSQL = sqlite3_mprintf(
     759             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     760             :                     "definition_12_063 IN ('%q', '%q')%s",
     761          12 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     762          12 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     763             :                     osEpochTest.c_str());
     764             :             }
     765             :         }
     766         307 :         else if (pszWKT1)
     767             :         {
     768             :             pszSQL =
     769         304 :                 sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     770             :                                 "definition = '%q'%s",
     771             :                                 pszWKT1.get(), osEpochTest.c_str());
     772             :         }
     773             :         else
     774             :         {
     775           3 :             pszSQL = nullptr;
     776             :         }
     777         349 :         if (pszSQL)
     778             :         {
     779         346 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     780         346 :             sqlite3_free(pszSQL);
     781         346 :             if (OGRERR_NONE == err)
     782             :             {
     783           5 :                 return nSRSId;
     784             :             }
     785             :         }
     786             :     }
     787             : 
     788         668 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     789         321 :         poSRSIn->GetCoordinateEpoch() == 0)
     790             :     {
     791         317 :         bool bTryToReuseSRSId = true;
     792         317 :         if (EQUAL(pszAuthorityName, "EPSG"))
     793             :         {
     794         632 :             OGRSpatialReference oSRS_EPSG;
     795         316 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     796             :                 OGRERR_NONE)
     797             :             {
     798         317 :                 if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
     799           1 :                     CPLTestBool(
     800             :                         CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
     801             :                 {
     802           1 :                     bTryToReuseSRSId = false;
     803           1 :                     CPLError(
     804             :                         CE_Warning, CPLE_AppDefined,
     805             :                         "Passed SRS uses %s:%d identification, but its "
     806             :                         "definition is not compatible with the "
     807             :                         "official definition of the object. "
     808             :                         "Registering it as a non-%s entry into the database.",
     809             :                         pszAuthorityName, nAuthorityCode, pszAuthorityName);
     810           1 :                     pszAuthorityName = nullptr;
     811           1 :                     nAuthorityCode = 0;
     812             :                 }
     813             :             }
     814             :         }
     815         317 :         if (bTryToReuseSRSId)
     816             :         {
     817             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     818         316 :             pszSQL = sqlite3_mprintf(
     819             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     820             :                 "srs_id = %d",
     821             :                 nAuthorityCode);
     822             : 
     823             :             // Yep, we can!
     824         316 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     825         315 :                 bCanUseAuthorityCode = true;
     826         316 :             sqlite3_free(pszSQL);
     827             :         }
     828             :     }
     829             : 
     830         347 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     831         347 :     bool bForceEpoch = false;
     832         350 :     if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
     833           3 :         (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
     834             :     {
     835           3 :         bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     836             :     }
     837             : 
     838             :     // Add epoch column if needed
     839         347 :     if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
     840             :     {
     841           3 :         if (m_bHasDefinition12_063)
     842             :         {
     843           0 :             if (SoftStartTransaction() != OGRERR_NONE)
     844           0 :                 return DEFAULT_SRID;
     845           0 :             if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
     846           0 :                                 "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
     847           0 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     848             :                                 "'gpkg_crs_wkt_1_1' "
     849             :                                 "WHERE extension_name = 'gpkg_crs_wkt'") !=
     850           0 :                     OGRERR_NONE ||
     851           0 :                 SQLCommand(
     852             :                     hDB,
     853             :                     "INSERT INTO gpkg_extensions "
     854             :                     "(table_name, column_name, extension_name, definition, "
     855             :                     "scope) "
     856             :                     "VALUES "
     857             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     858             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     859             :                     "'read-write')") != OGRERR_NONE)
     860             :             {
     861           0 :                 SoftRollbackTransaction();
     862           0 :                 return DEFAULT_SRID;
     863             :             }
     864             : 
     865           0 :             if (SoftCommitTransaction() != OGRERR_NONE)
     866           0 :                 return DEFAULT_SRID;
     867             : 
     868           0 :             m_bHasEpochColumn = true;
     869             :         }
     870             :         else
     871             :         {
     872           3 :             bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     873           3 :             bForceEpoch = true;
     874             :         }
     875             :     }
     876             : 
     877         353 :     if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
     878           6 :         !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
     879             :     {
     880           0 :         return DEFAULT_SRID;
     881             :     }
     882             : 
     883             :     // Reuse the authority code number as SRS_ID if we can
     884         347 :     if (bCanUseAuthorityCode)
     885             :     {
     886         315 :         nSRSId = nAuthorityCode;
     887             :     }
     888             :     // Otherwise, generate a new SRS_ID number (max + 1)
     889             :     else
     890             :     {
     891             :         // Get the current maximum srid in the srs table.
     892          32 :         const int nMaxSRSId = SQLGetInteger(
     893             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     894          32 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     895             :     }
     896             : 
     897         694 :     std::string osEpochColumn;
     898         347 :     std::string osEpochVal;
     899         347 :     if (poSRSIn->GetCoordinateEpoch() > 0)
     900             :     {
     901           5 :         osEpochColumn = ", epoch";
     902           5 :         osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
     903             :     }
     904             : 
     905             :     // Add new SRS row to gpkg_spatial_ref_sys.
     906         347 :     if (m_bHasDefinition12_063)
     907             :     {
     908             :         // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
     909          45 :         const char *pszWKT2 = poSRSIn->IsDynamic() &&
     910          10 :                                       poSRSIn->GetCoordinateEpoch() > 0 &&
     911           1 :                                       pszWKT2_2019
     912           1 :                                   ? pszWKT2_2019.get()
     913          44 :                               : pszWKT2_2015 ? pszWKT2_2015.get()
     914          97 :                                              : pszWKT2_2019.get();
     915             : 
     916          45 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     917             :         {
     918          99 :             pszSQL = sqlite3_mprintf(
     919             :                 "INSERT INTO gpkg_spatial_ref_sys "
     920             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     921             :                 "definition, definition_12_063%s) VALUES "
     922             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     923          33 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
     924             :                 pszAuthorityName, nAuthorityCode,
     925          62 :                 pszWKT1 ? pszWKT1.get() : "undefined",
     926             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     927             :         }
     928             :         else
     929             :         {
     930          36 :             pszSQL = sqlite3_mprintf(
     931             :                 "INSERT INTO gpkg_spatial_ref_sys "
     932             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     933             :                 "definition, definition_12_063%s) VALUES "
     934             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     935          12 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
     936          21 :                 nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
     937             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     938             :         }
     939             :     }
     940             :     else
     941             :     {
     942         302 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     943             :         {
     944         574 :             pszSQL = sqlite3_mprintf(
     945             :                 "INSERT INTO gpkg_spatial_ref_sys "
     946             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     947             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     948         287 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     949         574 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     950             :         }
     951             :         else
     952             :         {
     953          30 :             pszSQL = sqlite3_mprintf(
     954             :                 "INSERT INTO gpkg_spatial_ref_sys "
     955             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     956             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     957          15 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     958          30 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     959             :         }
     960             :     }
     961             : 
     962             :     // Add new row to gpkg_spatial_ref_sys.
     963         347 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     964             : 
     965             :     // Free everything that was allocated.
     966         347 :     sqlite3_free(pszSQL);
     967             : 
     968         347 :     return nSRSId;
     969             : }
     970             : 
     971             : /************************************************************************/
     972             : /*                       ~GDALGeoPackageDataset()                       */
     973             : /************************************************************************/
     974             : 
     975        5916 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
     976             : {
     977        2958 :     GDALGeoPackageDataset::Close();
     978        5916 : }
     979             : 
     980             : /************************************************************************/
     981             : /*                               Close()                                */
     982             : /************************************************************************/
     983             : 
     984        4957 : CPLErr GDALGeoPackageDataset::Close(GDALProgressFunc, void *)
     985             : {
     986        4957 :     CPLErr eErr = CE_None;
     987        4957 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     988             :     {
     989        1725 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
     990        4683 :             !m_osRasterTable.empty() && !m_bGeoTransformValid)
     991             :         {
     992           3 :             CPLError(CE_Failure, CPLE_AppDefined,
     993             :                      "Raster table %s not correctly initialized due to missing "
     994             :                      "call to SetGeoTransform()",
     995             :                      m_osRasterTable.c_str());
     996             :         }
     997             : 
     998        5897 :         if (!IsMarkedSuppressOnClose() &&
     999        2939 :             GDALGeoPackageDataset::FlushCache(true) != CE_None)
    1000             :         {
    1001           7 :             eErr = CE_Failure;
    1002             :         }
    1003             : 
    1004             :         // Destroy bands now since we don't want
    1005             :         // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
    1006             :         // destruction
    1007        4806 :         for (int i = 0; i < nBands; i++)
    1008        1848 :             delete papoBands[i];
    1009        2958 :         nBands = 0;
    1010        2958 :         CPLFree(papoBands);
    1011        2958 :         papoBands = nullptr;
    1012             : 
    1013             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1014             :         // need it
    1015        2958 :         m_apoOverviewDS.clear();
    1016             : 
    1017        2958 :         if (m_poParentDS)
    1018             :         {
    1019         330 :             hDB = nullptr;
    1020             :         }
    1021             : 
    1022        2958 :         m_apoLayers.clear();
    1023             : 
    1024        2958 :         m_oMapSrsIdToSrs.clear();
    1025             : 
    1026        2958 :         if (!CloseDB())
    1027           0 :             eErr = CE_Failure;
    1028             : 
    1029        2958 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1030           0 :             eErr = CE_Failure;
    1031             :     }
    1032        4957 :     return eErr;
    1033             : }
    1034             : 
    1035             : /************************************************************************/
    1036             : /*                          ICanIWriteBlock()                           */
    1037             : /************************************************************************/
    1038             : 
    1039        5699 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1040             : {
    1041        5699 :     if (!GetUpdate())
    1042             :     {
    1043           0 :         CPLError(
    1044             :             CE_Failure, CPLE_NotSupported,
    1045             :             "IWriteBlock() not supported on dataset opened in read-only mode");
    1046           0 :         return false;
    1047             :     }
    1048             : 
    1049        5699 :     if (m_pabyCachedTiles == nullptr)
    1050             :     {
    1051           0 :         return false;
    1052             :     }
    1053             : 
    1054        5699 :     if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
    1055             :     {
    1056           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1057             :                  "IWriteBlock() not supported if georeferencing not set");
    1058           0 :         return false;
    1059             :     }
    1060        5699 :     return true;
    1061             : }
    1062             : 
    1063             : /************************************************************************/
    1064             : /*                             IRasterIO()                              */
    1065             : /************************************************************************/
    1066             : 
    1067         137 : CPLErr GDALGeoPackageDataset::IRasterIO(
    1068             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1069             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1070             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1071             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1072             : 
    1073             : {
    1074         137 :     CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
    1075             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1076             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1077             :         psExtraArg);
    1078             : 
    1079             :     // If writing all bands, in non-shifted mode, flush all entirely written
    1080             :     // tiles This can avoid "stressing" the block cache with too many dirty
    1081             :     // blocks. Note: this logic would be useless with a per-dataset block cache.
    1082         137 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1083         126 :         nYSize == nBufYSize && nBandCount == nBands &&
    1084         123 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1085             :     {
    1086             :         auto poBand =
    1087         119 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1088             :         int nBlockXSize, nBlockYSize;
    1089         119 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1090         119 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1091         119 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1092         119 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1093         119 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1094         273 :         for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
    1095             :         {
    1096        4371 :             for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
    1097             :             {
    1098             :                 GDALRasterBlock *poBlock =
    1099        4217 :                     poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
    1100        4217 :                 if (poBlock)
    1101             :                 {
    1102             :                     // GetDirty() should be true in most situation (otherwise
    1103             :                     // it means the block cache is under extreme pressure!)
    1104        4215 :                     if (poBlock->GetDirty())
    1105             :                     {
    1106             :                         // IWriteBlock() on one band will check the dirty state
    1107             :                         // of the corresponding blocks in other bands, to decide
    1108             :                         // if it can call WriteTile(), so we have only to do
    1109             :                         // that on one of the bands
    1110        4215 :                         if (poBlock->Write() != CE_None)
    1111         250 :                             eErr = CE_Failure;
    1112             :                     }
    1113        4215 :                     poBlock->DropLock();
    1114             :                 }
    1115             :             }
    1116             :         }
    1117             :     }
    1118             : 
    1119         137 :     return eErr;
    1120             : }
    1121             : 
    1122             : /************************************************************************/
    1123             : /*                          GetOGRTableLimit()                          */
    1124             : /************************************************************************/
    1125             : 
    1126        4967 : static int GetOGRTableLimit()
    1127             : {
    1128        4967 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1129             : }
    1130             : 
    1131             : /************************************************************************/
    1132             : /*                   GetNameTypeMapFromSQliteMaster()                   */
    1133             : /************************************************************************/
    1134             : 
    1135             : const std::map<CPLString, CPLString> &
    1136        1591 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1137             : {
    1138        1591 :     if (!m_oMapNameToType.empty())
    1139         437 :         return m_oMapNameToType;
    1140             : 
    1141             :     CPLString osSQL(
    1142             :         "SELECT name, type FROM sqlite_master WHERE "
    1143             :         "type IN ('view', 'table') OR "
    1144        2308 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1145        1154 :     const int nTableLimit = GetOGRTableLimit();
    1146        1154 :     if (nTableLimit > 0)
    1147             :     {
    1148        1154 :         osSQL += " LIMIT ";
    1149        1154 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1150             :     }
    1151             : 
    1152        1154 :     auto oResult = SQLQuery(hDB, osSQL);
    1153        1154 :     if (oResult)
    1154             :     {
    1155       19171 :         for (int i = 0; i < oResult->RowCount(); i++)
    1156             :         {
    1157       18017 :             const char *pszName = oResult->GetValue(0, i);
    1158       18017 :             const char *pszType = oResult->GetValue(1, i);
    1159       18017 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1160             :         }
    1161             :     }
    1162             : 
    1163        1154 :     return m_oMapNameToType;
    1164             : }
    1165             : 
    1166             : /************************************************************************/
    1167             : /*                  RemoveTableFromSQLiteMasterCache()                  */
    1168             : /************************************************************************/
    1169             : 
    1170          58 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1171             :     const char *pszTableName)
    1172             : {
    1173          58 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1174          58 : }
    1175             : 
    1176             : /************************************************************************/
    1177             : /*                 GetUnknownExtensionsTableSpecific()                  */
    1178             : /************************************************************************/
    1179             : 
    1180             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1181        1065 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1182             : {
    1183        1065 :     if (m_bMapTableToExtensionsBuilt)
    1184         107 :         return m_oMapTableToExtensions;
    1185         958 :     m_bMapTableToExtensionsBuilt = true;
    1186             : 
    1187         958 :     if (!HasExtensionsTable())
    1188          52 :         return m_oMapTableToExtensions;
    1189             : 
    1190             :     CPLString osSQL(
    1191             :         "SELECT table_name, extension_name, definition, scope "
    1192             :         "FROM gpkg_extensions WHERE "
    1193             :         "table_name IS NOT NULL "
    1194             :         "AND extension_name IS NOT NULL "
    1195             :         "AND definition IS NOT NULL "
    1196             :         "AND scope IS NOT NULL "
    1197             :         "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
    1198             :         "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
    1199             :         "'gpkg_geom_MULTICURVE', "
    1200             :         "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
    1201             :         "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
    1202             :         "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
    1203             :         "'gpkg_srs_id_trigger', "
    1204             :         "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
    1205             :         "'gpkg_related_tables', 'related_tables'"
    1206             : #ifdef HAVE_SPATIALITE
    1207             :         ", 'gdal_spatialite_computed_geom_column'"
    1208             : #endif
    1209        1812 :         ")");
    1210         906 :     const int nTableLimit = GetOGRTableLimit();
    1211         906 :     if (nTableLimit > 0)
    1212             :     {
    1213         906 :         osSQL += " LIMIT ";
    1214         906 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1215             :     }
    1216             : 
    1217         906 :     auto oResult = SQLQuery(hDB, osSQL);
    1218         906 :     if (oResult)
    1219             :     {
    1220        1725 :         for (int i = 0; i < oResult->RowCount(); i++)
    1221             :         {
    1222         819 :             const char *pszTableName = oResult->GetValue(0, i);
    1223         819 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1224         819 :             const char *pszDefinition = oResult->GetValue(2, i);
    1225         819 :             const char *pszScope = oResult->GetValue(3, i);
    1226         819 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1227             :             {
    1228         819 :                 GPKGExtensionDesc oDesc;
    1229         819 :                 oDesc.osExtensionName = pszExtensionName;
    1230         819 :                 oDesc.osDefinition = pszDefinition;
    1231         819 :                 oDesc.osScope = pszScope;
    1232        1638 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1233         819 :                     .push_back(std::move(oDesc));
    1234             :             }
    1235             :         }
    1236             :     }
    1237             : 
    1238         906 :     return m_oMapTableToExtensions;
    1239             : }
    1240             : 
    1241             : /************************************************************************/
    1242             : /*                            GetContents()                             */
    1243             : /************************************************************************/
    1244             : 
    1245             : const std::map<CPLString, GPKGContentsDesc> &
    1246        1046 : GDALGeoPackageDataset::GetContents()
    1247             : {
    1248        1046 :     if (m_bMapTableToContentsBuilt)
    1249          90 :         return m_oMapTableToContents;
    1250         956 :     m_bMapTableToContentsBuilt = true;
    1251             : 
    1252             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1253             :                     "description, min_x, min_y, max_x, max_y "
    1254        1912 :                     "FROM gpkg_contents");
    1255         956 :     const int nTableLimit = GetOGRTableLimit();
    1256         956 :     if (nTableLimit > 0)
    1257             :     {
    1258         956 :         osSQL += " LIMIT ";
    1259         956 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1260             :     }
    1261             : 
    1262         956 :     auto oResult = SQLQuery(hDB, osSQL);
    1263         956 :     if (oResult)
    1264             :     {
    1265        2059 :         for (int i = 0; i < oResult->RowCount(); i++)
    1266             :         {
    1267        1103 :             const char *pszTableName = oResult->GetValue(0, i);
    1268        1103 :             if (pszTableName == nullptr)
    1269           0 :                 continue;
    1270        1103 :             const char *pszDataType = oResult->GetValue(1, i);
    1271        1103 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1272        1103 :             const char *pszDescription = oResult->GetValue(3, i);
    1273        1103 :             const char *pszMinX = oResult->GetValue(4, i);
    1274        1103 :             const char *pszMinY = oResult->GetValue(5, i);
    1275        1103 :             const char *pszMaxX = oResult->GetValue(6, i);
    1276        1103 :             const char *pszMaxY = oResult->GetValue(7, i);
    1277        1103 :             GPKGContentsDesc oDesc;
    1278        1103 :             if (pszDataType)
    1279        1103 :                 oDesc.osDataType = pszDataType;
    1280        1103 :             if (pszIdentifier)
    1281        1103 :                 oDesc.osIdentifier = pszIdentifier;
    1282        1103 :             if (pszDescription)
    1283        1102 :                 oDesc.osDescription = pszDescription;
    1284        1103 :             if (pszMinX)
    1285         761 :                 oDesc.osMinX = pszMinX;
    1286        1103 :             if (pszMinY)
    1287         761 :                 oDesc.osMinY = pszMinY;
    1288        1103 :             if (pszMaxX)
    1289         761 :                 oDesc.osMaxX = pszMaxX;
    1290        1103 :             if (pszMaxY)
    1291         761 :                 oDesc.osMaxY = pszMaxY;
    1292        2206 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1293        2206 :                 std::move(oDesc);
    1294             :         }
    1295             :     }
    1296             : 
    1297         956 :     return m_oMapTableToContents;
    1298             : }
    1299             : 
    1300             : /************************************************************************/
    1301             : /*                                Open()                                */
    1302             : /************************************************************************/
    1303             : 
    1304        1477 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1305             :                                 const std::string &osFilenameInZip)
    1306             : {
    1307        1477 :     m_osFilenameInZip = osFilenameInZip;
    1308        1477 :     CPLAssert(m_apoLayers.empty());
    1309        1477 :     CPLAssert(hDB == nullptr);
    1310             : 
    1311        1477 :     SetDescription(poOpenInfo->pszFilename);
    1312        2954 :     CPLString osFilename(poOpenInfo->pszFilename);
    1313        2954 :     CPLString osSubdatasetTableName;
    1314             :     GByte abyHeaderLetMeHerePlease[100];
    1315        1477 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1316        1477 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
    1317             :     {
    1318         313 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    1319             :                                                 CSLT_HONOURSTRINGS);
    1320         313 :         int nCount = CSLCount(papszTokens);
    1321         313 :         if (nCount < 2)
    1322             :         {
    1323           0 :             CSLDestroy(papszTokens);
    1324           0 :             return FALSE;
    1325             :         }
    1326             : 
    1327         313 :         if (nCount <= 3)
    1328             :         {
    1329         311 :             osFilename = papszTokens[1];
    1330             :         }
    1331             :         /* GPKG:C:\BLA.GPKG:foo */
    1332           2 :         else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
    1333           2 :                  (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
    1334             :         {
    1335           2 :             osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1336             :         }
    1337             :         // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
    1338           0 :         else if (/*nCount >= 4 && */
    1339           0 :                  (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1340           0 :                   EQUAL(papszTokens[1], "/vsicurl/https")))
    1341             :         {
    1342           0 :             osFilename = CPLString(papszTokens[1]);
    1343           0 :             for (int i = 2; i < nCount - 1; i++)
    1344             :             {
    1345           0 :                 osFilename += ':';
    1346           0 :                 osFilename += papszTokens[i];
    1347             :             }
    1348             :         }
    1349         313 :         if (nCount >= 3)
    1350          14 :             osSubdatasetTableName = papszTokens[nCount - 1];
    1351             : 
    1352         313 :         CSLDestroy(papszTokens);
    1353         313 :         VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    1354         313 :         if (fp != nullptr)
    1355             :         {
    1356         313 :             VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
    1357         313 :             VSIFCloseL(fp);
    1358             :         }
    1359         313 :         pabyHeader = abyHeaderLetMeHerePlease;
    1360             :     }
    1361        1164 :     else if (poOpenInfo->pabyHeader &&
    1362        1164 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1363             :                          "SQLite format 3"))
    1364             :     {
    1365        1157 :         m_bCallUndeclareFileNotToOpen = true;
    1366        1157 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1367             :                                          poOpenInfo->nHeaderBytes);
    1368             :     }
    1369             : 
    1370        1477 :     eAccess = poOpenInfo->eAccess;
    1371        1477 :     if (!m_osFilenameInZip.empty())
    1372             :     {
    1373           2 :         m_pszFilename = CPLStrdup(CPLSPrintf(
    1374             :             "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
    1375             :     }
    1376             :     else
    1377             :     {
    1378        1475 :         m_pszFilename = CPLStrdup(osFilename);
    1379             :     }
    1380             : 
    1381        1477 :     if (poOpenInfo->papszOpenOptions)
    1382             :     {
    1383         100 :         CSLDestroy(papszOpenOptions);
    1384         100 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1385             :     }
    1386             : 
    1387             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1388        1477 :     if (poOpenInfo->pabyHeader &&
    1389        1164 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1390           5 :                     "-- SQL GPKG") &&
    1391           5 :         poOpenInfo->fpL != nullptr)
    1392             :     {
    1393           5 :         if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
    1394             :             SQLITE_OK)
    1395             :         {
    1396           0 :             return FALSE;
    1397             :         }
    1398             : 
    1399           5 :         InstallSQLFunctions();
    1400             : 
    1401             :         // Ingest the lines of the dump
    1402           5 :         VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
    1403             :         const char *pszLine;
    1404          76 :         while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
    1405             :         {
    1406          71 :             if (STARTS_WITH(pszLine, "--"))
    1407           5 :                 continue;
    1408             : 
    1409          66 :             if (!SQLCheckLineIsSafe(pszLine))
    1410           0 :                 return false;
    1411             : 
    1412          66 :             char *pszErrMsg = nullptr;
    1413          66 :             if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
    1414             :                 SQLITE_OK)
    1415             :             {
    1416           0 :                 if (pszErrMsg)
    1417           0 :                     CPLDebug("SQLITE", "Error %s", pszErrMsg);
    1418             :             }
    1419          66 :             sqlite3_free(pszErrMsg);
    1420           5 :         }
    1421             :     }
    1422             : 
    1423        1472 :     else if (pabyHeader != nullptr)
    1424             : #endif
    1425             :     {
    1426        1472 :         if (poOpenInfo->fpL)
    1427             :         {
    1428             :             // See above comment about -wal locking for the importance of
    1429             :             // closing that file, prior to calling sqlite3_open()
    1430        1040 :             VSIFCloseL(poOpenInfo->fpL);
    1431        1040 :             poOpenInfo->fpL = nullptr;
    1432             :         }
    1433             : 
    1434             :         /* See if we can open the SQLite database */
    1435        1472 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1436             :                                         : SQLITE_OPEN_READONLY))
    1437           3 :             return FALSE;
    1438             : 
    1439        1469 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1440        1469 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1441        1469 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1442        1469 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1443        1469 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1444             :         {
    1445           9 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1446             :         }
    1447        1460 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1448             :         {
    1449           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1450             :         }
    1451        1458 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1452        1454 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1453             :         {
    1454        1452 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1455        1452 :                      (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    1456             :         }
    1457             :     }
    1458             : 
    1459             :     /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
    1460             :      * “ok” */
    1461             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1462             :     /* Disable integrity check by default, since it is expensive on big files */
    1463        1474 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
    1464           0 :         OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
    1465             :     {
    1466           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1467             :                  "pragma integrity_check on '%s' failed", m_pszFilename);
    1468           0 :         return FALSE;
    1469             :     }
    1470             : 
    1471             :     /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
    1472             :     /* parameter value SHALL return an empty result set */
    1473             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1474             :     /* Disable the check by default, since it is to corrupt databases, and */
    1475             :     /* that causes issues to downstream software that can't open them. */
    1476        1474 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
    1477           0 :         OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
    1478             :     {
    1479           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1480             :                  "pragma foreign_key_check on '%s' failed.", m_pszFilename);
    1481           0 :         return FALSE;
    1482             :     }
    1483             : 
    1484             :     /* Check for requirement metadata tables */
    1485             :     /* Requirement 10: gpkg_spatial_ref_sys must exist */
    1486             :     /* Requirement 13: gpkg_contents must exist */
    1487        1474 :     if (SQLGetInteger(hDB,
    1488             :                       "SELECT COUNT(*) FROM sqlite_master WHERE "
    1489             :                       "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
    1490             :                       "type IN ('table', 'view')",
    1491        1474 :                       nullptr) != 2)
    1492             :     {
    1493           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1494             :                  "At least one of the required GeoPackage tables, "
    1495             :                  "gpkg_spatial_ref_sys or gpkg_contents, is missing");
    1496           0 :         return FALSE;
    1497             :     }
    1498             : 
    1499        1474 :     DetectSpatialRefSysColumns();
    1500             : 
    1501             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1502        1474 :     if (SQLGetInteger(hDB,
    1503             :                       "SELECT 1 FROM sqlite_master WHERE "
    1504             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1505        1474 :                       nullptr) == 1)
    1506             :     {
    1507        1463 :         m_bHasGPKGOGRContents = true;
    1508             :     }
    1509             : #endif
    1510             : 
    1511        1474 :     CheckUnknownExtensions();
    1512             : 
    1513        1474 :     int bRet = FALSE;
    1514        1474 :     bool bHasGPKGExtRelations = false;
    1515        1474 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1516             :     {
    1517        1281 :         m_bHasGPKGGeometryColumns =
    1518        1281 :             SQLGetInteger(hDB,
    1519             :                           "SELECT 1 FROM sqlite_master WHERE "
    1520             :                           "name = 'gpkg_geometry_columns' AND "
    1521             :                           "type IN ('table', 'view')",
    1522        1281 :                           nullptr) == 1;
    1523        1281 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1524             :     }
    1525        1474 :     if (m_bHasGPKGGeometryColumns)
    1526             :     {
    1527             :         /* Load layer definitions for all tables in gpkg_contents &
    1528             :          * gpkg_geometry_columns */
    1529             :         /* and non-spatial tables as well */
    1530             :         std::string osSQL =
    1531             :             "SELECT c.table_name, c.identifier, 1 as is_spatial, "
    1532             :             "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
    1533             :             "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
    1534             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1535             :             "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
    1536             :             "  FROM gpkg_geometry_columns g "
    1537             :             "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
    1538             :             "  WHERE "
    1539             :             "  c.table_name <> 'ogr_empty_table' AND"
    1540             :             "  c.data_type = 'features' "
    1541             :             // aspatial: Was the only method available in OGR 2.0 and 2.1
    1542             :             // attributes: GPKG 1.2 or later
    1543             :             "UNION ALL "
    1544             :             "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
    1545             :             "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
    1546             :             "is_in_gpkg_contents, "
    1547             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1548             :             "lower(table_name) AND type IN ('table', 'view')) AS object_type "
    1549             :             "  FROM gpkg_contents"
    1550        1279 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1551             : 
    1552        2558 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1553        1279 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1554        1279 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1555        1279 :         if (!bHasASpatialOrAttributes)
    1556             :         {
    1557             :             auto oResultTable =
    1558             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1559        1278 :                               "data_type = 'attributes' LIMIT 1");
    1560        1278 :             bHasASpatialOrAttributes =
    1561        1278 :                 (oResultTable && oResultTable->RowCount() == 1);
    1562             :         }
    1563        1279 :         if (bHasGPKGExtRelations)
    1564             :         {
    1565             :             osSQL += "UNION ALL "
    1566             :                      "SELECT mapping_table_name, mapping_table_name, 0 as "
    1567             :                      "is_spatial, NULL, NULL, 0, 0, 0 AS "
    1568             :                      "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1569             :                      "is_in_gpkg_contents, 'table' AS object_type "
    1570             :                      "FROM gpkgext_relations WHERE "
    1571             :                      "lower(mapping_table_name) NOT IN (SELECT "
    1572             :                      "lower(table_name) FROM gpkg_contents) AND "
    1573             :                      "EXISTS (SELECT 1 FROM sqlite_master WHERE "
    1574             :                      "type IN ('table', 'view') AND "
    1575          20 :                      "lower(name) = lower(mapping_table_name))";
    1576             :         }
    1577        1279 :         if (EQUAL(pszListAllTables, "YES") ||
    1578        1278 :             (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
    1579             :         {
    1580             :             // vgpkg_ is Spatialite virtual table
    1581             :             osSQL +=
    1582             :                 "UNION ALL "
    1583             :                 "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
    1584             :                 "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1585             :                 "is_in_gpkg_contents, type AS object_type "
    1586             :                 "FROM sqlite_master WHERE type IN ('table', 'view') "
    1587             :                 "AND name NOT LIKE 'gpkg_%' "
    1588             :                 "AND name NOT LIKE 'vgpkg_%' "
    1589             :                 "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
    1590             :                 // Avoid reading those views from simple_sewer_features.gpkg
    1591             :                 "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
    1592             :                 "'st_geometry_columns', 'geometry_columns') "
    1593             :                 "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
    1594        1209 :                 "gpkg_contents)";
    1595        1209 :             if (bHasGPKGExtRelations)
    1596             :             {
    1597             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1598             :                          "lower(mapping_table_name) FROM "
    1599          15 :                          "gpkgext_relations)";
    1600             :             }
    1601             :         }
    1602        1279 :         const int nTableLimit = GetOGRTableLimit();
    1603        1279 :         if (nTableLimit > 0)
    1604             :         {
    1605        1279 :             osSQL += " LIMIT ";
    1606        1279 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1607             :         }
    1608             : 
    1609        1279 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1610        1279 :         if (!oResult)
    1611             :         {
    1612           0 :             return FALSE;
    1613             :         }
    1614             : 
    1615        1279 :         if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1616             :         {
    1617           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1618             :                      "File has more than %d vector tables. "
    1619             :                      "Limiting to first %d (can be overridden with "
    1620             :                      "OGR_TABLE_LIMIT config option)",
    1621             :                      nTableLimit, nTableLimit);
    1622           1 :             oResult->LimitRowCount(nTableLimit);
    1623             :         }
    1624             : 
    1625        1279 :         if (oResult->RowCount() > 0)
    1626             :         {
    1627        1162 :             bRet = TRUE;
    1628             : 
    1629        1162 :             m_apoLayers.reserve(oResult->RowCount());
    1630             : 
    1631        2324 :             std::map<std::string, int> oMapTableRefCount;
    1632        4552 :             for (int i = 0; i < oResult->RowCount(); i++)
    1633             :             {
    1634        3390 :                 const char *pszTableName = oResult->GetValue(0, i);
    1635        3390 :                 if (pszTableName == nullptr)
    1636           0 :                     continue;
    1637        3390 :                 if (++oMapTableRefCount[pszTableName] == 2)
    1638             :                 {
    1639             :                     // This should normally not happen if all constraints are
    1640             :                     // properly set
    1641           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1642             :                              "Table %s appearing several times in "
    1643             :                              "gpkg_contents and/or gpkg_geometry_columns",
    1644             :                              pszTableName);
    1645             :                 }
    1646             :             }
    1647             : 
    1648        2324 :             std::set<std::string> oExistingLayers;
    1649        4552 :             for (int i = 0; i < oResult->RowCount(); i++)
    1650             :             {
    1651        3390 :                 const char *pszTableName = oResult->GetValue(0, i);
    1652        3390 :                 if (pszTableName == nullptr)
    1653           2 :                     continue;
    1654             :                 const bool bTableHasSeveralGeomColumns =
    1655        3390 :                     oMapTableRefCount[pszTableName] > 1;
    1656        3390 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1657        3390 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1658        3390 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1659        3390 :                 const char *pszZ = oResult->GetValue(5, i);
    1660        3390 :                 const char *pszM = oResult->GetValue(6, i);
    1661             :                 bool bIsInGpkgContents =
    1662        3390 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1663        3390 :                 if (!bIsInGpkgContents)
    1664          46 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1665        3390 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1666        3390 :                 if (pszObjectType == nullptr ||
    1667        3389 :                     !(EQUAL(pszObjectType, "table") ||
    1668          21 :                       EQUAL(pszObjectType, "view")))
    1669             :                 {
    1670           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1671             :                              "Table/view %s is referenced in gpkg_contents, "
    1672             :                              "but does not exist",
    1673             :                              pszTableName);
    1674           1 :                     continue;
    1675             :                 }
    1676             :                 // Non-standard and undocumented behavior:
    1677             :                 // if the same table appears to have several geometry columns,
    1678             :                 // handle it for now as multiple layers named
    1679             :                 // "table_name (geom_col_name)"
    1680             :                 // The way we handle that might change in the future (e.g
    1681             :                 // could be a single layer with multiple geometry columns)
    1682             :                 std::string osLayerNameWithGeomColName =
    1683        7196 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1684             :                                          pszGeomColName + ')'
    1685        6778 :                                    : std::string(pszTableName);
    1686        3389 :                 if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
    1687           1 :                     continue;
    1688        3388 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1689             :                 const std::string osLayerName =
    1690             :                     bTableHasSeveralGeomColumns
    1691           3 :                         ? std::move(osLayerNameWithGeomColName)
    1692        6779 :                         : std::string(pszTableName);
    1693             :                 auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
    1694        6776 :                     this, osLayerName.c_str());
    1695        3388 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1696        3388 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1697        3388 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1698             :                 {
    1699         726 :                     if (pszZ && atoi(pszZ) == 2)
    1700          14 :                         bHasZ = false;
    1701         726 :                     if (pszM && atoi(pszM) == 2)
    1702           6 :                         bHasM = false;
    1703             :                 }
    1704        3388 :                 poLayer->SetOpeningParameters(
    1705             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1706             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1707        3388 :                 m_apoLayers.push_back(std::move(poLayer));
    1708             :             }
    1709             :         }
    1710             :     }
    1711             : 
    1712        1474 :     bool bHasTileMatrixSet = false;
    1713        1474 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1714             :     {
    1715         675 :         bHasTileMatrixSet = SQLGetInteger(hDB,
    1716             :                                           "SELECT 1 FROM sqlite_master WHERE "
    1717             :                                           "name = 'gpkg_tile_matrix_set' AND "
    1718             :                                           "type IN ('table', 'view')",
    1719             :                                           nullptr) == 1;
    1720             :     }
    1721        1474 :     if (bHasTileMatrixSet)
    1722             :     {
    1723             :         std::string osSQL =
    1724             :             "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
    1725             :             "c.min_x, c.min_y, c.max_x, c.max_y, "
    1726             :             "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
    1727             :             "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
    1728             :             "c.table_name = tms.table_name WHERE "
    1729         672 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1730         672 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1731             :             osSubdatasetTableName =
    1732           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1733         672 :         if (!osSubdatasetTableName.empty())
    1734             :         {
    1735          16 :             char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
    1736             :                                            osSubdatasetTableName.c_str());
    1737          16 :             osSQL += pszTmp;
    1738          16 :             sqlite3_free(pszTmp);
    1739          16 :             SetPhysicalFilename(osFilename.c_str());
    1740             :         }
    1741         672 :         const int nTableLimit = GetOGRTableLimit();
    1742         672 :         if (nTableLimit > 0)
    1743             :         {
    1744         672 :             osSQL += " LIMIT ";
    1745         672 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1746             :         }
    1747             : 
    1748         672 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1749         672 :         if (!oResult)
    1750             :         {
    1751           0 :             return FALSE;
    1752             :         }
    1753             : 
    1754         672 :         if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
    1755             :         {
    1756           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1757             :                      "Cannot find table '%s' in GeoPackage dataset",
    1758             :                      osSubdatasetTableName.c_str());
    1759             :         }
    1760         671 :         else if (oResult->RowCount() == 1)
    1761             :         {
    1762         285 :             const char *pszTableName = oResult->GetValue(0, 0);
    1763         285 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1764         285 :             const char *pszDescription = oResult->GetValue(2, 0);
    1765         285 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1766         285 :             const char *pszMinX = oResult->GetValue(4, 0);
    1767         285 :             const char *pszMinY = oResult->GetValue(5, 0);
    1768         285 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1769         285 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1770         285 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1771         285 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1772         285 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1773         285 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1774         285 :             const char *pszDataType = oResult->GetValue(12, 0);
    1775         285 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1776             :                 pszTMSMaxY)
    1777             :             {
    1778         570 :                 bRet = OpenRaster(
    1779             :                     pszTableName, pszIdentifier, pszDescription,
    1780         285 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1781             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1782             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1783         285 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1784             :             }
    1785             :         }
    1786         386 :         else if (oResult->RowCount() >= 1)
    1787             :         {
    1788           5 :             bRet = TRUE;
    1789             : 
    1790           5 :             if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1791             :             {
    1792           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1793             :                          "File has more than %d raster tables. "
    1794             :                          "Limiting to first %d (can be overridden with "
    1795             :                          "OGR_TABLE_LIMIT config option)",
    1796             :                          nTableLimit, nTableLimit);
    1797           1 :                 oResult->LimitRowCount(nTableLimit);
    1798             :             }
    1799             : 
    1800           5 :             int nSDSCount = 0;
    1801        2013 :             for (int i = 0; i < oResult->RowCount(); i++)
    1802             :             {
    1803        2008 :                 const char *pszTableName = oResult->GetValue(0, i);
    1804        2008 :                 const char *pszIdentifier = oResult->GetValue(1, i);
    1805        2008 :                 if (pszTableName == nullptr)
    1806           0 :                     continue;
    1807             :                 m_aosSubDatasets.AddNameValue(
    1808             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
    1809        2008 :                     CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
    1810             :                 m_aosSubDatasets.AddNameValue(
    1811             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
    1812             :                     pszIdentifier
    1813        2008 :                         ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
    1814        4016 :                         : pszTableName);
    1815        2008 :                 nSDSCount++;
    1816             :             }
    1817             :         }
    1818             :     }
    1819             : 
    1820        1474 :     if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
    1821             :     {
    1822          34 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
    1823             :         {
    1824          23 :             bRet = TRUE;
    1825             :         }
    1826             :         else
    1827             :         {
    1828          11 :             CPLDebug("GPKG",
    1829             :                      "This GeoPackage has no vector content and is opened "
    1830             :                      "in read-only mode. If you open it in update mode, "
    1831             :                      "opening will be successful.");
    1832             :         }
    1833             :     }
    1834             : 
    1835        1474 :     if (eAccess == GA_Update)
    1836             :     {
    1837         285 :         FixupWrongRTreeTrigger();
    1838         285 :         FixupWrongMedataReferenceColumnNameUpdate();
    1839             :     }
    1840             : 
    1841        1474 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1842             : 
    1843        1474 :     return bRet;
    1844             : }
    1845             : 
    1846             : /************************************************************************/
    1847             : /*                     DetectSpatialRefSysColumns()                     */
    1848             : /************************************************************************/
    1849             : 
    1850        1484 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1851             : {
    1852             :     // Detect definition_12_063 column
    1853             :     {
    1854        1484 :         sqlite3_stmt *hSQLStmt = nullptr;
    1855        1484 :         int rc = sqlite3_prepare_v2(
    1856             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1857             :             &hSQLStmt, nullptr);
    1858        1484 :         if (rc == SQLITE_OK)
    1859             :         {
    1860          85 :             m_bHasDefinition12_063 = true;
    1861          85 :             sqlite3_finalize(hSQLStmt);
    1862             :         }
    1863             :     }
    1864             : 
    1865             :     // Detect epoch column
    1866        1484 :     if (m_bHasDefinition12_063)
    1867             :     {
    1868          85 :         sqlite3_stmt *hSQLStmt = nullptr;
    1869             :         int rc =
    1870          85 :             sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
    1871             :                                -1, &hSQLStmt, nullptr);
    1872          85 :         if (rc == SQLITE_OK)
    1873             :         {
    1874          76 :             m_bHasEpochColumn = true;
    1875          76 :             sqlite3_finalize(hSQLStmt);
    1876             :         }
    1877             :     }
    1878        1484 : }
    1879             : 
    1880             : /************************************************************************/
    1881             : /*                       FixupWrongRTreeTrigger()                       */
    1882             : /************************************************************************/
    1883             : 
    1884         285 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    1885             : {
    1886             :     auto oResult = SQLQuery(
    1887             :         hDB,
    1888             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    1889         285 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    1890         285 :     if (oResult == nullptr)
    1891           0 :         return;
    1892         285 :     if (oResult->RowCount() > 0)
    1893             :     {
    1894           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    1895             :     }
    1896         287 :     for (int i = 0; i < oResult->RowCount(); i++)
    1897             :     {
    1898           2 :         const char *pszName = oResult->GetValue(0, i);
    1899           2 :         const char *pszSQL = oResult->GetValue(1, i);
    1900           2 :         const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
    1901           2 :         if (pszPtr1)
    1902             :         {
    1903           2 :             const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
    1904             :             // Skipping over geometry column name
    1905           4 :             while (*pszPtr == ' ')
    1906           2 :                 pszPtr++;
    1907           2 :             if (pszPtr[0] == '"' || pszPtr[0] == '\'')
    1908             :             {
    1909           1 :                 char chStringDelim = pszPtr[0];
    1910           1 :                 pszPtr++;
    1911           9 :                 while (*pszPtr != '\0' && *pszPtr != chStringDelim)
    1912             :                 {
    1913           8 :                     if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
    1914           0 :                         pszPtr += 2;
    1915             :                     else
    1916           8 :                         pszPtr += 1;
    1917             :                 }
    1918           1 :                 if (*pszPtr == chStringDelim)
    1919           1 :                     pszPtr++;
    1920             :             }
    1921             :             else
    1922             :             {
    1923           1 :                 pszPtr++;
    1924           8 :                 while (*pszPtr != ' ')
    1925           7 :                     pszPtr++;
    1926             :             }
    1927           2 :             if (*pszPtr == ' ')
    1928             :             {
    1929           2 :                 SQLCommand(hDB,
    1930           4 :                            ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
    1931             :                                .c_str());
    1932           4 :                 CPLString newSQL;
    1933           2 :                 newSQL.assign(pszSQL, pszPtr1 - pszSQL);
    1934           2 :                 newSQL += " AFTER UPDATE";
    1935           2 :                 newSQL += pszPtr;
    1936           2 :                 SQLCommand(hDB, newSQL);
    1937             :             }
    1938             :         }
    1939             :     }
    1940             : }
    1941             : 
    1942             : /************************************************************************/
    1943             : /*             FixupWrongMedataReferenceColumnNameUpdate()              */
    1944             : /************************************************************************/
    1945             : 
    1946         285 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
    1947             : {
    1948             :     // Fix wrong trigger that was generated by GDAL < 2.4.0
    1949             :     // See https://github.com/qgis/QGIS/issues/42768
    1950             :     auto oResult = SQLQuery(
    1951             :         hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
    1952             :              "NAME ='gpkg_metadata_reference_column_name_update' AND "
    1953         285 :              "sql LIKE '%column_nameIS%'");
    1954         285 :     if (oResult == nullptr)
    1955           0 :         return;
    1956         285 :     if (oResult->RowCount() == 1)
    1957             :     {
    1958           1 :         CPLDebug("GPKG", "Fixing incorrect trigger "
    1959             :                          "gpkg_metadata_reference_column_name_update");
    1960           1 :         const char *pszSQL = oResult->GetValue(0, 0);
    1961             :         std::string osNewSQL(
    1962           3 :             CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
    1963             : 
    1964           1 :         SQLCommand(hDB,
    1965             :                    "DROP TRIGGER gpkg_metadata_reference_column_name_update");
    1966           1 :         SQLCommand(hDB, osNewSQL.c_str());
    1967             :     }
    1968             : }
    1969             : 
    1970             : /************************************************************************/
    1971             : /*                      ClearCachedRelationships()                      */
    1972             : /************************************************************************/
    1973             : 
    1974          38 : void GDALGeoPackageDataset::ClearCachedRelationships()
    1975             : {
    1976          38 :     m_bHasPopulatedRelationships = false;
    1977          38 :     m_osMapRelationships.clear();
    1978          38 : }
    1979             : 
    1980             : /************************************************************************/
    1981             : /*                         LoadRelationships()                          */
    1982             : /************************************************************************/
    1983             : 
    1984         106 : void GDALGeoPackageDataset::LoadRelationships() const
    1985             : {
    1986         106 :     m_osMapRelationships.clear();
    1987             : 
    1988         106 :     std::vector<std::string> oExcludedTables;
    1989         106 :     if (HasGpkgextRelationsTable())
    1990             :     {
    1991          41 :         LoadRelationshipsUsingRelatedTablesExtension();
    1992             : 
    1993          98 :         for (const auto &oRelationship : m_osMapRelationships)
    1994             :         {
    1995             :             oExcludedTables.emplace_back(
    1996          57 :                 oRelationship.second->GetMappingTableName());
    1997             :         }
    1998             :     }
    1999             : 
    2000             :     // Also load relationships defined using foreign keys (i.e. one-to-many
    2001             :     // relationships). Here we must exclude any relationships defined from the
    2002             :     // related tables extension, we don't want them included twice.
    2003         106 :     LoadRelationshipsFromForeignKeys(oExcludedTables);
    2004         106 :     m_bHasPopulatedRelationships = true;
    2005         106 : }
    2006             : 
    2007             : /************************************************************************/
    2008             : /*            LoadRelationshipsUsingRelatedTablesExtension()            */
    2009             : /************************************************************************/
    2010             : 
    2011          41 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
    2012             : {
    2013          41 :     m_osMapRelationships.clear();
    2014             : 
    2015             :     auto oResultTable = SQLQuery(
    2016          41 :         hDB, "SELECT base_table_name, base_primary_column, "
    2017             :              "related_table_name, related_primary_column, relation_name, "
    2018          82 :              "mapping_table_name FROM gpkgext_relations");
    2019          41 :     if (oResultTable && oResultTable->RowCount() > 0)
    2020             :     {
    2021          95 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    2022             :         {
    2023          58 :             const char *pszBaseTableName = oResultTable->GetValue(0, i);
    2024          58 :             if (!pszBaseTableName)
    2025             :             {
    2026           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2027             :                          "Could not retrieve base_table_name from "
    2028             :                          "gpkgext_relations");
    2029           1 :                 continue;
    2030             :             }
    2031          58 :             const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
    2032          58 :             if (!pszBasePrimaryColumn)
    2033             :             {
    2034           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2035             :                          "Could not retrieve base_primary_column from "
    2036             :                          "gpkgext_relations");
    2037           0 :                 continue;
    2038             :             }
    2039          58 :             const char *pszRelatedTableName = oResultTable->GetValue(2, i);
    2040          58 :             if (!pszRelatedTableName)
    2041             :             {
    2042           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2043             :                          "Could not retrieve related_table_name from "
    2044             :                          "gpkgext_relations");
    2045           0 :                 continue;
    2046             :             }
    2047          58 :             const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
    2048          58 :             if (!pszRelatedPrimaryColumn)
    2049             :             {
    2050           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2051             :                          "Could not retrieve related_primary_column from "
    2052             :                          "gpkgext_relations");
    2053           0 :                 continue;
    2054             :             }
    2055          58 :             const char *pszRelationName = oResultTable->GetValue(4, i);
    2056          58 :             if (!pszRelationName)
    2057             :             {
    2058           0 :                 CPLError(
    2059             :                     CE_Warning, CPLE_AppDefined,
    2060             :                     "Could not retrieve relation_name from gpkgext_relations");
    2061           0 :                 continue;
    2062             :             }
    2063          58 :             const char *pszMappingTableName = oResultTable->GetValue(5, i);
    2064          58 :             if (!pszMappingTableName)
    2065             :             {
    2066           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2067             :                          "Could not retrieve mapping_table_name from "
    2068             :                          "gpkgext_relations");
    2069           0 :                 continue;
    2070             :             }
    2071             : 
    2072             :             // confirm that mapping table exists
    2073             :             char *pszSQL =
    2074          58 :                 sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
    2075             :                                 "name='%q' AND type IN ('table', 'view')",
    2076             :                                 pszMappingTableName);
    2077          58 :             const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
    2078          58 :             sqlite3_free(pszSQL);
    2079             : 
    2080          59 :             if (nMappingTableCount < 1 &&
    2081           1 :                 !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    2082           1 :                     pszMappingTableName))
    2083             :             {
    2084           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2085             :                          "Relationship mapping table %s does not exist",
    2086             :                          pszMappingTableName);
    2087           1 :                 continue;
    2088             :             }
    2089             : 
    2090             :             const std::string osRelationName = GenerateNameForRelationship(
    2091         114 :                 pszBaseTableName, pszRelatedTableName, pszRelationName);
    2092             : 
    2093         114 :             std::string osType{};
    2094             :             // defined requirement classes -- for these types the relation name
    2095             :             // will be specific string value from the related tables extension.
    2096             :             // In this case we need to construct a unique relationship name
    2097             :             // based on the related tables
    2098          57 :             if (EQUAL(pszRelationName, "media") ||
    2099          42 :                 EQUAL(pszRelationName, "simple_attributes") ||
    2100          42 :                 EQUAL(pszRelationName, "features") ||
    2101          20 :                 EQUAL(pszRelationName, "attributes") ||
    2102           2 :                 EQUAL(pszRelationName, "tiles"))
    2103             :             {
    2104          55 :                 osType = pszRelationName;
    2105             :             }
    2106             :             else
    2107             :             {
    2108             :                 // user defined types default to features
    2109           2 :                 osType = "features";
    2110             :             }
    2111             : 
    2112             :             auto poRelationship = std::make_unique<GDALRelationship>(
    2113             :                 osRelationName, pszBaseTableName, pszRelatedTableName,
    2114         114 :                 GRC_MANY_TO_MANY);
    2115             : 
    2116         114 :             poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
    2117         114 :             poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
    2118         114 :             poRelationship->SetLeftMappingTableFields({"base_id"});
    2119         114 :             poRelationship->SetRightMappingTableFields({"related_id"});
    2120          57 :             poRelationship->SetMappingTableName(pszMappingTableName);
    2121          57 :             poRelationship->SetRelatedTableType(osType);
    2122             : 
    2123          57 :             m_osMapRelationships[osRelationName] = std::move(poRelationship);
    2124             :         }
    2125             :     }
    2126          41 : }
    2127             : 
    2128             : /************************************************************************/
    2129             : /*                    GenerateNameForRelationship()                     */
    2130             : /************************************************************************/
    2131             : 
    2132          83 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
    2133             :     const char *pszBaseTableName, const char *pszRelatedTableName,
    2134             :     const char *pszType)
    2135             : {
    2136             :     // defined requirement classes -- for these types the relation name will be
    2137             :     // specific string value from the related tables extension. In this case we
    2138             :     // need to construct a unique relationship name based on the related tables
    2139          83 :     if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
    2140          55 :         EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
    2141           8 :         EQUAL(pszType, "tiles"))
    2142             :     {
    2143         150 :         std::ostringstream stream;
    2144             :         stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
    2145          75 :                << pszType;
    2146          75 :         return stream.str();
    2147             :     }
    2148             :     else
    2149             :     {
    2150             :         // user defined types default to features
    2151           8 :         return pszType;
    2152             :     }
    2153             : }
    2154             : 
    2155             : /************************************************************************/
    2156             : /*                        ValidateRelationship()                        */
    2157             : /************************************************************************/
    2158             : 
    2159          30 : bool GDALGeoPackageDataset::ValidateRelationship(
    2160             :     const GDALRelationship *poRelationship, std::string &failureReason)
    2161             : {
    2162             : 
    2163          30 :     if (poRelationship->GetCardinality() !=
    2164             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2165             :     {
    2166           3 :         failureReason = "Only many to many relationships are supported";
    2167           3 :         return false;
    2168             :     }
    2169             : 
    2170          54 :     std::string osRelatedTableType = poRelationship->GetRelatedTableType();
    2171          71 :     if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
    2172          32 :         osRelatedTableType != "media" &&
    2173          20 :         osRelatedTableType != "simple_attributes" &&
    2174          59 :         osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
    2175             :     {
    2176             :         failureReason =
    2177           4 :             ("Related table type " + osRelatedTableType +
    2178             :              " is not a valid value for the GeoPackage specification. "
    2179             :              "Valid values are: features, media, simple_attributes, "
    2180             :              "attributes, tiles.")
    2181           2 :                 .c_str();
    2182           2 :         return false;
    2183             :     }
    2184             : 
    2185          25 :     const std::string &osLeftTableName = poRelationship->GetLeftTableName();
    2186          25 :     OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2187          25 :         GetLayerByName(osLeftTableName.c_str()));
    2188          25 :     if (!poLeftTable)
    2189             :     {
    2190           4 :         failureReason = ("Left table " + osLeftTableName +
    2191             :                          " is not an existing layer in the dataset")
    2192           2 :                             .c_str();
    2193           2 :         return false;
    2194             :     }
    2195          23 :     const std::string &osRightTableName = poRelationship->GetRightTableName();
    2196          23 :     OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2197          23 :         GetLayerByName(osRightTableName.c_str()));
    2198          23 :     if (!poRightTable)
    2199             :     {
    2200           4 :         failureReason = ("Right table " + osRightTableName +
    2201             :                          " is not an existing layer in the dataset")
    2202           2 :                             .c_str();
    2203           2 :         return false;
    2204             :     }
    2205             : 
    2206          21 :     const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
    2207          21 :     if (aosLeftTableFields.empty())
    2208             :     {
    2209           1 :         failureReason = "No left table fields were specified";
    2210           1 :         return false;
    2211             :     }
    2212          20 :     else if (aosLeftTableFields.size() > 1)
    2213             :     {
    2214             :         failureReason = "Only a single left table field is permitted for the "
    2215           1 :                         "GeoPackage specification";
    2216           1 :         return false;
    2217             :     }
    2218             :     else
    2219             :     {
    2220             :         // validate left field exists
    2221          38 :         if (poLeftTable->GetLayerDefn()->GetFieldIndex(
    2222          43 :                 aosLeftTableFields[0].c_str()) < 0 &&
    2223           5 :             !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
    2224             :         {
    2225           2 :             failureReason = ("Left table field " + aosLeftTableFields[0] +
    2226           2 :                              " does not exist in " + osLeftTableName)
    2227           1 :                                 .c_str();
    2228           1 :             return false;
    2229             :         }
    2230             :     }
    2231             : 
    2232          18 :     const auto &aosRightTableFields = poRelationship->GetRightTableFields();
    2233          18 :     if (aosRightTableFields.empty())
    2234             :     {
    2235           1 :         failureReason = "No right table fields were specified";
    2236           1 :         return false;
    2237             :     }
    2238          17 :     else if (aosRightTableFields.size() > 1)
    2239             :     {
    2240             :         failureReason = "Only a single right table field is permitted for the "
    2241           1 :                         "GeoPackage specification";
    2242           1 :         return false;
    2243             :     }
    2244             :     else
    2245             :     {
    2246             :         // validate right field exists
    2247          32 :         if (poRightTable->GetLayerDefn()->GetFieldIndex(
    2248          38 :                 aosRightTableFields[0].c_str()) < 0 &&
    2249           6 :             !EQUAL(poRightTable->GetFIDColumn(),
    2250             :                    aosRightTableFields[0].c_str()))
    2251             :         {
    2252           4 :             failureReason = ("Right table field " + aosRightTableFields[0] +
    2253           4 :                              " does not exist in " + osRightTableName)
    2254           2 :                                 .c_str();
    2255           2 :             return false;
    2256             :         }
    2257             :     }
    2258             : 
    2259          14 :     return true;
    2260             : }
    2261             : 
    2262             : /************************************************************************/
    2263             : /*                             InitRaster()                             */
    2264             : /************************************************************************/
    2265             : 
    2266         369 : bool GDALGeoPackageDataset::InitRaster(
    2267             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
    2268             :     double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2269             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2270             :     const char *pszContentsMaxY, CSLConstList papszOpenOptionsIn,
    2271             :     const SQLResult &oResult, int nIdxInResult)
    2272             : {
    2273         369 :     m_osRasterTable = pszTableName;
    2274         369 :     m_dfTMSMinX = dfMinX;
    2275         369 :     m_dfTMSMaxY = dfMaxY;
    2276             : 
    2277             :     // Despite prior checking, the type might be Binary and
    2278             :     // SQLResultGetValue() not working properly on it
    2279         369 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2280         369 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2281             :     {
    2282           0 :         return false;
    2283             :     }
    2284         369 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2285         369 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2286         369 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2287             :     {
    2288           0 :         return false;
    2289             :     }
    2290         369 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2291         369 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2292         369 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2293             :         nTileHeight > 65536)
    2294             :     {
    2295           0 :         return false;
    2296             :     }
    2297             :     int nTileMatrixWidth = static_cast<int>(
    2298         738 :         std::min(static_cast<GIntBig>(INT_MAX),
    2299         369 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2300             :     int nTileMatrixHeight = static_cast<int>(
    2301         738 :         std::min(static_cast<GIntBig>(INT_MAX),
    2302         369 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2303         369 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2304             :     {
    2305           0 :         return false;
    2306             :     }
    2307             : 
    2308             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2309         369 :     double dfGDALMinX = dfMinX;
    2310         369 :     double dfGDALMinY = dfMinY;
    2311         369 :     double dfGDALMaxX = dfMaxX;
    2312         369 :     double dfGDALMaxY = dfMaxY;
    2313             :     pszContentsMinX =
    2314         369 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2315             :     pszContentsMinY =
    2316         369 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2317             :     pszContentsMaxX =
    2318         369 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2319             :     pszContentsMaxY =
    2320         369 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2321         369 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2322         369 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2323             :     {
    2324         737 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2325         368 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2326             :         {
    2327         368 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2328         368 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2329         368 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2330         368 :             dfGDALMaxY = CPLAtof(pszContentsMaxY);
    2331             :         }
    2332             :         else
    2333             :         {
    2334           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2335             :                      "Illegal min_x/min_y/max_x/max_y values for %s in open "
    2336             :                      "options and/or gpkg_contents. Using bounds of "
    2337             :                      "gpkg_tile_matrix_set instead",
    2338             :                      pszTableName);
    2339             :         }
    2340             :     }
    2341         369 :     if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
    2342             :     {
    2343           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2344             :                  "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
    2345           0 :         return false;
    2346             :     }
    2347             : 
    2348         369 :     int nBandCount = 0;
    2349             :     const char *pszBAND_COUNT =
    2350         369 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2351         369 :     if (poParentDS)
    2352             :     {
    2353          86 :         nBandCount = poParentDS->GetRasterCount();
    2354             :     }
    2355         283 :     else if (m_eDT != GDT_UInt8)
    2356             :     {
    2357          65 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
    2358           0 :             !EQUAL(pszBAND_COUNT, "1"))
    2359             :         {
    2360           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2361             :                      "BAND_COUNT ignored for non-Byte data");
    2362             :         }
    2363          65 :         nBandCount = 1;
    2364             :     }
    2365             :     else
    2366             :     {
    2367         218 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
    2368             :         {
    2369          69 :             nBandCount = atoi(pszBAND_COUNT);
    2370          69 :             if (nBandCount == 1)
    2371           5 :                 GetMetadata(GDAL_MDD_IMAGE_STRUCTURE);
    2372             :         }
    2373             :         else
    2374             :         {
    2375         149 :             GetMetadata(GDAL_MDD_IMAGE_STRUCTURE);
    2376         149 :             nBandCount = m_nBandCountFromMetadata;
    2377         149 :             if (nBandCount == 1)
    2378          47 :                 m_eTF = GPKG_TF_PNG;
    2379             :         }
    2380         218 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2381             :         {
    2382           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2383             :         }
    2384         218 :         if (nBandCount <= 0 || nBandCount > 4)
    2385          88 :             nBandCount = 4;
    2386             :     }
    2387             : 
    2388         369 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2389             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2390             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2391         369 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2392             : }
    2393             : 
    2394             : /************************************************************************/
    2395             : /*                     ComputeTileAndPixelShifts()                      */
    2396             : /************************************************************************/
    2397             : 
    2398         806 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2399             : {
    2400             :     int nTileWidth, nTileHeight;
    2401         806 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2402             : 
    2403             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2404         806 :     const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
    2405         806 :     if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
    2406         803 :           dfShiftXPixels / nTileWidth < INT_MAX))
    2407             :     {
    2408           3 :         return false;
    2409             :     }
    2410         803 :     const int64_t nShiftXPixels =
    2411         803 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2412         803 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2413         803 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2414          11 :         m_nShiftXTiles--;
    2415         803 :     m_nShiftXPixelsMod =
    2416         803 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2417             :         nTileWidth;
    2418             : 
    2419         803 :     const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
    2420         803 :     if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
    2421         803 :           dfShiftYPixels / nTileHeight < INT_MAX))
    2422             :     {
    2423           1 :         return false;
    2424             :     }
    2425         802 :     const int64_t nShiftYPixels =
    2426         802 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2427         802 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2428         802 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2429          11 :         m_nShiftYTiles--;
    2430         802 :     m_nShiftYPixelsMod =
    2431         802 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2432             :         nTileHeight;
    2433         802 :     return true;
    2434             : }
    2435             : 
    2436             : /************************************************************************/
    2437             : /*                          AllocCachedTiles()                          */
    2438             : /************************************************************************/
    2439             : 
    2440         802 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2441             : {
    2442             :     int nTileWidth, nTileHeight;
    2443         802 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2444             : 
    2445             :     // We currently need 4 caches because of
    2446             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2447         802 :     const int nCacheCount = 4;
    2448             :     /*
    2449             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2450             :             (GetUpdate() && m_eDT == GDT_UInt8) ? 2 : 1;
    2451             :     */
    2452         802 :     m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
    2453             :         cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_UInt8 ? 4 : 1) *
    2454             :                           m_nDTSize),
    2455             :         nTileWidth, nTileHeight));
    2456         802 :     if (m_pabyCachedTiles == nullptr)
    2457             :     {
    2458           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
    2459             :                  nTileWidth, nTileHeight);
    2460           0 :         return false;
    2461             :     }
    2462             : 
    2463         802 :     return true;
    2464             : }
    2465             : 
    2466             : /************************************************************************/
    2467             : /*                             InitRaster()                             */
    2468             : /************************************************************************/
    2469             : 
    2470         613 : bool GDALGeoPackageDataset::InitRaster(
    2471             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
    2472             :     int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
    2473             :     double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
    2474             :     int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
    2475             :     double dfGDALMaxX, double dfGDALMaxY)
    2476             : {
    2477         613 :     m_osRasterTable = pszTableName;
    2478         613 :     m_dfTMSMinX = dfTMSMinX;
    2479         613 :     m_dfTMSMaxY = dfTMSMaxY;
    2480         613 :     m_nZoomLevel = nZoomLevel;
    2481         613 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2482         613 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2483             : 
    2484         613 :     m_bGeoTransformValid = true;
    2485         613 :     m_gt[0] = dfGDALMinX;
    2486         613 :     m_gt[1] = dfPixelXSize;
    2487         613 :     m_gt[3] = dfGDALMaxY;
    2488         613 :     m_gt[5] = -dfPixelYSize;
    2489         613 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2490         613 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2491         613 :     if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2492             :     {
    2493           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
    2494             :                  dfRasterXSize, dfRasterYSize);
    2495           0 :         return false;
    2496             :     }
    2497         613 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2498         613 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2499             : 
    2500         613 :     if (poParentDS)
    2501             :     {
    2502         330 :         m_poParentDS = poParentDS;
    2503         330 :         eAccess = poParentDS->eAccess;
    2504         330 :         hDB = poParentDS->hDB;
    2505         330 :         m_eTF = poParentDS->m_eTF;
    2506         330 :         m_eDT = poParentDS->m_eDT;
    2507         330 :         m_nDTSize = poParentDS->m_nDTSize;
    2508         330 :         m_dfScale = poParentDS->m_dfScale;
    2509         330 :         m_dfOffset = poParentDS->m_dfOffset;
    2510         330 :         m_dfPrecision = poParentDS->m_dfPrecision;
    2511         330 :         m_usGPKGNull = poParentDS->m_usGPKGNull;
    2512         330 :         m_nQuality = poParentDS->m_nQuality;
    2513         330 :         m_nZLevel = poParentDS->m_nZLevel;
    2514         330 :         m_bDither = poParentDS->m_bDither;
    2515             :         /*m_nSRID = poParentDS->m_nSRID;*/
    2516         330 :         m_osWHERE = poParentDS->m_osWHERE;
    2517         330 :         SetDescription(CPLSPrintf("%s - zoom_level=%d",
    2518         330 :                                   poParentDS->GetDescription(), m_nZoomLevel));
    2519             :     }
    2520             : 
    2521        2132 :     for (int i = 1; i <= nBandCount; i++)
    2522             :     {
    2523             :         auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
    2524        1519 :             this, nTileWidth, nTileHeight);
    2525        1519 :         if (poParentDS)
    2526             :         {
    2527         766 :             int bHasNoData = FALSE;
    2528             :             double dfNoDataValue =
    2529         766 :                 poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    2530         766 :             if (bHasNoData)
    2531          24 :                 poNewBand->SetNoDataValueInternal(dfNoDataValue);
    2532             :         }
    2533             : 
    2534        1519 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2535             :         {
    2536           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2537             :         }
    2538        1519 :         if (!m_osNodataValueFromMetadata.empty())
    2539             :         {
    2540           8 :             poNewBand->SetNoDataValueInternal(
    2541             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2542             :         }
    2543             : 
    2544        1519 :         SetBand(i, std::move(poNewBand));
    2545             :     }
    2546             : 
    2547         613 :     if (!ComputeTileAndPixelShifts())
    2548             :     {
    2549           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2550             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2551           3 :         return false;
    2552             :     }
    2553             : 
    2554         610 :     GDALPamDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
    2555             :                                     GDAL_MDD_IMAGE_STRUCTURE);
    2556         610 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2557             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2558             : 
    2559         610 :     return AllocCachedTiles();
    2560             : }
    2561             : 
    2562             : /************************************************************************/
    2563             : /*                    GDALGPKGMBTilesGetTileFormat()                    */
    2564             : /************************************************************************/
    2565             : 
    2566          90 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
    2567             : {
    2568          90 :     GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
    2569          90 :     if (pszTF)
    2570             :     {
    2571          90 :         if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
    2572           1 :             eTF = GPKG_TF_PNG_JPEG;
    2573          89 :         else if (EQUAL(pszTF, "PNG"))
    2574          48 :             eTF = GPKG_TF_PNG;
    2575          41 :         else if (EQUAL(pszTF, "PNG8"))
    2576          10 :             eTF = GPKG_TF_PNG8;
    2577          31 :         else if (EQUAL(pszTF, "JPEG"))
    2578          16 :             eTF = GPKG_TF_JPEG;
    2579          15 :         else if (EQUAL(pszTF, "WEBP"))
    2580          15 :             eTF = GPKG_TF_WEBP;
    2581             :         else
    2582             :         {
    2583           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2584             :                      "Unsuppoted value for TILE_FORMAT: %s", pszTF);
    2585             :         }
    2586             :     }
    2587          90 :     return eTF;
    2588             : }
    2589             : 
    2590          38 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
    2591             : {
    2592          38 :     switch (eTF)
    2593             :     {
    2594          32 :         case GPKG_TF_PNG:
    2595             :         case GPKG_TF_PNG8:
    2596          32 :             return "png";
    2597           3 :         case GPKG_TF_JPEG:
    2598           3 :             return "jpg";
    2599           3 :         case GPKG_TF_WEBP:
    2600           3 :             return "webp";
    2601           0 :         default:
    2602           0 :             break;
    2603             :     }
    2604           0 :     CPLError(CE_Failure, CPLE_NotSupported,
    2605             :              "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
    2606           0 :     return nullptr;
    2607             : }
    2608             : 
    2609             : /************************************************************************/
    2610             : /*                             OpenRaster()                             */
    2611             : /************************************************************************/
    2612             : 
    2613         285 : bool GDALGeoPackageDataset::OpenRaster(
    2614             :     const char *pszTableName, const char *pszIdentifier,
    2615             :     const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
    2616             :     double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2617             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2618             :     const char *pszContentsMaxY, bool bIsTiles, CSLConstList papszOpenOptionsIn)
    2619             : {
    2620         285 :     if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
    2621           0 :         return false;
    2622             : 
    2623             :     // Config option just for debug, and for example force set to NaN
    2624             :     // which is not supported
    2625         570 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2626         570 :     CPLString osUom;
    2627         570 :     CPLString osFieldName;
    2628         570 :     CPLString osGridCellEncoding;
    2629         285 :     if (!bIsTiles)
    2630             :     {
    2631          65 :         char *pszSQL = sqlite3_mprintf(
    2632             :             "SELECT datatype, scale, offset, data_null, precision FROM "
    2633             :             "gpkg_2d_gridded_coverage_ancillary "
    2634             :             "WHERE tile_matrix_set_name = '%q' "
    2635             :             "AND datatype IN ('integer', 'float')"
    2636             :             "AND (scale > 0 OR scale IS NULL)",
    2637             :             pszTableName);
    2638          65 :         auto oResult = SQLQuery(hDB, pszSQL);
    2639          65 :         sqlite3_free(pszSQL);
    2640          65 :         if (!oResult || oResult->RowCount() == 0)
    2641             :         {
    2642           0 :             return false;
    2643             :         }
    2644          65 :         const char *pszDataType = oResult->GetValue(0, 0);
    2645          65 :         const char *pszScale = oResult->GetValue(1, 0);
    2646          65 :         const char *pszOffset = oResult->GetValue(2, 0);
    2647          65 :         const char *pszDataNull = oResult->GetValue(3, 0);
    2648          65 :         const char *pszPrecision = oResult->GetValue(4, 0);
    2649          65 :         if (pszDataNull)
    2650          23 :             osDataNull = pszDataNull;
    2651          65 :         if (EQUAL(pszDataType, "float"))
    2652             :         {
    2653           6 :             SetDataType(GDT_Float32);
    2654           6 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    2655             :         }
    2656             :         else
    2657             :         {
    2658          59 :             SetDataType(GDT_Float32);
    2659          59 :             m_eTF = GPKG_TF_PNG_16BIT;
    2660          59 :             const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
    2661          59 :             const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
    2662          59 :             if (dfScale == 1.0)
    2663             :             {
    2664          59 :                 if (dfOffset == 0.0)
    2665             :                 {
    2666          24 :                     SetDataType(GDT_UInt16);
    2667             :                 }
    2668          35 :                 else if (dfOffset == -32768.0)
    2669             :                 {
    2670          35 :                     SetDataType(GDT_Int16);
    2671             :                 }
    2672             :                 // coverity[tainted_data]
    2673           0 :                 else if (dfOffset == -32767.0 && !osDataNull.empty() &&
    2674           0 :                          CPLAtof(osDataNull) == 65535.0)
    2675             :                 // Given that we will map the nodata value to -32768
    2676             :                 {
    2677           0 :                     SetDataType(GDT_Int16);
    2678             :                 }
    2679             :             }
    2680             : 
    2681             :             // Check that the tile offset and scales are compatible with a
    2682             :             // final integer result.
    2683          59 :             if (m_eDT != GDT_Float32)
    2684             :             {
    2685             :                 // coverity[tainted_data]
    2686          59 :                 if (dfScale == 1.0 && dfOffset == -32768.0 &&
    2687         118 :                     !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
    2688             :                 {
    2689             :                     // Given that we will map the nodata value to -32768
    2690           9 :                     pszSQL = sqlite3_mprintf(
    2691             :                         "SELECT 1 FROM "
    2692             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2693             :                         "tpudt_name = '%q' "
    2694             :                         "AND NOT ((offset = 0.0 or offset = 1.0) "
    2695             :                         "AND scale = 1.0) "
    2696             :                         "LIMIT 1",
    2697             :                         pszTableName);
    2698             :                 }
    2699             :                 else
    2700             :                 {
    2701          50 :                     pszSQL = sqlite3_mprintf(
    2702             :                         "SELECT 1 FROM "
    2703             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2704             :                         "tpudt_name = '%q' "
    2705             :                         "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
    2706             :                         pszTableName);
    2707             :                 }
    2708          59 :                 sqlite3_stmt *hSQLStmt = nullptr;
    2709             :                 int rc =
    2710          59 :                     SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
    2711             : 
    2712          59 :                 if (rc == SQLITE_OK)
    2713             :                 {
    2714          59 :                     if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
    2715             :                     {
    2716           8 :                         SetDataType(GDT_Float32);
    2717             :                     }
    2718          59 :                     sqlite3_finalize(hSQLStmt);
    2719             :                 }
    2720          59 :                 sqlite3_free(pszSQL);
    2721             :             }
    2722             : 
    2723          59 :             SetGlobalOffsetScale(dfOffset, dfScale);
    2724             :         }
    2725          65 :         if (pszPrecision)
    2726          65 :             m_dfPrecision = CPLAtof(pszPrecision);
    2727             : 
    2728             :         // Request those columns in a separate query, so as to keep
    2729             :         // compatibility with pre OGC 17-066r1 databases
    2730             :         pszSQL =
    2731          65 :             sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
    2732             :                             "gpkg_2d_gridded_coverage_ancillary "
    2733             :                             "WHERE tile_matrix_set_name = '%q'",
    2734             :                             pszTableName);
    2735          65 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    2736          65 :         oResult = SQLQuery(hDB, pszSQL);
    2737          65 :         CPLPopErrorHandler();
    2738          65 :         sqlite3_free(pszSQL);
    2739          65 :         if (oResult && oResult->RowCount() == 1)
    2740             :         {
    2741          64 :             const char *pszUom = oResult->GetValue(0, 0);
    2742          64 :             if (pszUom)
    2743           2 :                 osUom = pszUom;
    2744          64 :             const char *pszFieldName = oResult->GetValue(1, 0);
    2745          64 :             if (pszFieldName)
    2746          64 :                 osFieldName = pszFieldName;
    2747          64 :             const char *pszGridCellEncoding = oResult->GetValue(2, 0);
    2748          64 :             if (pszGridCellEncoding)
    2749          64 :                 osGridCellEncoding = pszGridCellEncoding;
    2750             :         }
    2751             :     }
    2752             : 
    2753         285 :     m_bRecordInsertedInGPKGContent = true;
    2754         285 :     m_nSRID = nSRSId;
    2755             : 
    2756         569 :     if (auto poSRS = GetSpatialRef(nSRSId))
    2757             :     {
    2758         284 :         m_oSRS = *(poSRS.get());
    2759             :     }
    2760             : 
    2761             :     /* Various sanity checks added in the SELECT */
    2762         285 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2763         570 :     CPLString osQuotedTableName(pszQuotedTableName);
    2764         285 :     sqlite3_free(pszQuotedTableName);
    2765         285 :     char *pszSQL = sqlite3_mprintf(
    2766             :         "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
    2767             :         "tile_height, matrix_width, matrix_height "
    2768             :         "FROM gpkg_tile_matrix tm "
    2769             :         "WHERE table_name = %s "
    2770             :         // INT_MAX would be the theoretical maximum value to avoid
    2771             :         // overflows, but that's already a insane value.
    2772             :         "AND zoom_level >= 0 AND zoom_level <= 65536 "
    2773             :         "AND pixel_x_size > 0 AND pixel_y_size > 0 "
    2774             :         "AND tile_width >= 1 AND tile_width <= 65536 "
    2775             :         "AND tile_height >= 1 AND tile_height <= 65536 "
    2776             :         "AND matrix_width >= 1 AND matrix_height >= 1",
    2777             :         osQuotedTableName.c_str());
    2778         570 :     CPLString osSQL(pszSQL);
    2779             :     const char *pszZoomLevel =
    2780         285 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2781         285 :     if (pszZoomLevel)
    2782             :     {
    2783           5 :         if (GetUpdate())
    2784           1 :             osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
    2785             :         else
    2786             :         {
    2787             :             osSQL += CPLSPrintf(
    2788             :                 " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
    2789             :                 "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
    2790             :                 atoi(pszZoomLevel), atoi(pszZoomLevel),
    2791           4 :                 osQuotedTableName.c_str());
    2792             :         }
    2793             :     }
    2794             :     // In read-only mode, only lists non empty zoom levels
    2795         280 :     else if (!GetUpdate())
    2796             :     {
    2797             :         osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
    2798             :                             "tm.zoom_level LIMIT 1)",
    2799         226 :                             osQuotedTableName.c_str());
    2800             :     }
    2801             :     else  // if( pszZoomLevel == nullptr )
    2802             :     {
    2803             :         osSQL +=
    2804             :             CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
    2805          54 :                        osQuotedTableName.c_str());
    2806             :     }
    2807         285 :     osSQL += " ORDER BY zoom_level DESC";
    2808             :     // To avoid denial of service.
    2809         285 :     osSQL += " LIMIT 100";
    2810             : 
    2811         570 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2812         285 :     if (!oResult || oResult->RowCount() == 0)
    2813             :     {
    2814         120 :         if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
    2815         120 :             pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
    2816             :             pszContentsMaxY != nullptr)
    2817             :         {
    2818          59 :             osSQL = pszSQL;
    2819          59 :             osSQL += " ORDER BY zoom_level DESC";
    2820          59 :             if (!GetUpdate())
    2821          33 :                 osSQL += " LIMIT 1";
    2822          59 :             oResult = SQLQuery(hDB, osSQL.c_str());
    2823             :         }
    2824          60 :         if (!oResult || oResult->RowCount() == 0)
    2825             :         {
    2826           1 :             if (oResult && pszZoomLevel != nullptr)
    2827             :             {
    2828           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2829             :                          "ZOOM_LEVEL is probably not valid w.r.t tile "
    2830             :                          "table content");
    2831             :             }
    2832           1 :             sqlite3_free(pszSQL);
    2833           1 :             return false;
    2834             :         }
    2835             :     }
    2836         284 :     sqlite3_free(pszSQL);
    2837             : 
    2838             :     // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
    2839             :     // actually exist.
    2840             : 
    2841             :     // CAUTION: Do not move those variables inside inner scope !
    2842         568 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2843             : 
    2844         284 :     if (CPLTestBool(
    2845             :             CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
    2846             :     {
    2847          13 :         pszSQL = sqlite3_mprintf(
    2848             :             "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
    2849             :             "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
    2850             :             pszTableName, atoi(oResult->GetValue(0, 0)));
    2851          13 :         auto oResult2 = SQLQuery(hDB, pszSQL);
    2852          13 :         sqlite3_free(pszSQL);
    2853          26 :         if (!oResult2 || oResult2->RowCount() == 0 ||
    2854             :             // Can happen if table is empty
    2855          38 :             oResult2->GetValue(0, 0) == nullptr ||
    2856             :             // Can happen if table has no NOT NULL constraint on tile_row
    2857             :             // and that all tile_row are NULL
    2858          12 :             oResult2->GetValue(1, 0) == nullptr)
    2859             :         {
    2860           1 :             return false;
    2861             :         }
    2862          12 :         const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
    2863          12 :         const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
    2864          12 :         const int nTileWidth = atoi(oResult->GetValue(3, 0));
    2865          12 :         const int nTileHeight = atoi(oResult->GetValue(4, 0));
    2866             :         osContentsMinX =
    2867          24 :             CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2868          12 :                                              atoi(oResult2->GetValue(0, 0)));
    2869             :         osContentsMaxY =
    2870          24 :             CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2871          12 :                                              atoi(oResult2->GetValue(1, 0)));
    2872             :         osContentsMaxX = CPLSPrintf(
    2873          24 :             "%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2874          12 :                                   (1 + atoi(oResult2->GetValue(2, 0))));
    2875             :         osContentsMinY = CPLSPrintf(
    2876          24 :             "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2877          12 :                                   (1 + atoi(oResult2->GetValue(3, 0))));
    2878          12 :         pszContentsMinX = osContentsMinX.c_str();
    2879          12 :         pszContentsMinY = osContentsMinY.c_str();
    2880          12 :         pszContentsMaxX = osContentsMaxX.c_str();
    2881          12 :         pszContentsMaxY = osContentsMaxY.c_str();
    2882             :     }
    2883             : 
    2884         283 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    2885             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    2886         283 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    2887             :     {
    2888           3 :         return false;
    2889             :     }
    2890             : 
    2891         280 :     auto poBand = cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    2892         280 :     if (!osDataNull.empty())
    2893             :     {
    2894          23 :         double dfGPKGNoDataValue = CPLAtof(osDataNull);
    2895          23 :         if (m_eTF == GPKG_TF_PNG_16BIT)
    2896             :         {
    2897          21 :             if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
    2898          21 :                 static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
    2899             :             {
    2900           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2901             :                          "data_null = %.17g is invalid for integer data_type",
    2902             :                          dfGPKGNoDataValue);
    2903             :             }
    2904             :             else
    2905             :             {
    2906          21 :                 m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
    2907          21 :                 if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
    2908           9 :                     dfGPKGNoDataValue = -32768.0;
    2909          12 :                 else if (m_eDT == GDT_Float32)
    2910             :                 {
    2911             :                     // Pick a value that is unlikely to be hit with offset &
    2912             :                     // scale
    2913           4 :                     dfGPKGNoDataValue = -std::numeric_limits<float>::max();
    2914             :                 }
    2915          21 :                 poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
    2916             :             }
    2917             :         }
    2918             :         else
    2919             :         {
    2920           2 :             poBand->SetNoDataValueInternal(
    2921           2 :                 static_cast<float>(dfGPKGNoDataValue));
    2922             :         }
    2923             :     }
    2924         280 :     if (!osUom.empty())
    2925             :     {
    2926           2 :         poBand->SetUnitTypeInternal(osUom);
    2927             :     }
    2928         280 :     if (!osFieldName.empty())
    2929             :     {
    2930          64 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    2931             :     }
    2932         280 :     if (!osGridCellEncoding.empty())
    2933             :     {
    2934          64 :         if (osGridCellEncoding == "grid-value-is-center")
    2935             :         {
    2936          15 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2937             :                                             GDALMD_AOP_POINT);
    2938             :         }
    2939          49 :         else if (osGridCellEncoding == "grid-value-is-area")
    2940             :         {
    2941          45 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2942             :                                             GDALMD_AOP_AREA);
    2943             :         }
    2944             :         else
    2945             :         {
    2946           4 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    2947             :                                             GDALMD_AOP_POINT);
    2948           4 :             GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
    2949             :                 "GRID_CELL_ENCODING", osGridCellEncoding);
    2950             :         }
    2951             :     }
    2952             : 
    2953         280 :     CheckUnknownExtensions(true);
    2954             : 
    2955             :     // Do this after CheckUnknownExtensions() so that m_eTF is set to
    2956             :     // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
    2957         280 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    2958         280 :     if (pszTF)
    2959             :     {
    2960           4 :         if (!GetUpdate())
    2961             :         {
    2962           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2963             :                      "TILE_FORMAT open option ignored in read-only mode");
    2964             :         }
    2965           4 :         else if (m_eTF == GPKG_TF_PNG_16BIT ||
    2966           4 :                  m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    2967             :         {
    2968           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2969             :                      "TILE_FORMAT open option ignored on gridded coverages");
    2970             :         }
    2971             :         else
    2972             :         {
    2973           4 :             GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    2974           4 :             if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
    2975             :             {
    2976           1 :                 if (!RegisterWebPExtension())
    2977           0 :                     return false;
    2978             :             }
    2979           4 :             m_eTF = eTF;
    2980             :         }
    2981             :     }
    2982             : 
    2983         280 :     ParseCompressionOptions(papszOpenOptionsIn);
    2984             : 
    2985         280 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    2986             : 
    2987             :     // Set metadata
    2988         280 :     if (pszIdentifier && pszIdentifier[0])
    2989         280 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    2990         280 :     if (pszDescription && pszDescription[0])
    2991          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    2992             : 
    2993             :     // Add overviews
    2994         365 :     for (int i = 1; i < oResult->RowCount(); i++)
    2995             :     {
    2996          86 :         auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    2997          86 :         poOvrDS->ShareLockWithParentDataset(this);
    2998         172 :         if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
    2999             :                                  dfMaxY, pszContentsMinX, pszContentsMinY,
    3000             :                                  pszContentsMaxX, pszContentsMaxY,
    3001          86 :                                  papszOpenOptionsIn, *oResult, i))
    3002             :         {
    3003           0 :             break;
    3004             :         }
    3005             : 
    3006             :         int nTileWidth, nTileHeight;
    3007          86 :         poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3008             :         const bool bStop =
    3009          87 :             (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
    3010           1 :              poOvrDS->GetRasterYSize() < nTileHeight);
    3011             : 
    3012          86 :         m_apoOverviewDS.push_back(std::move(poOvrDS));
    3013             : 
    3014          86 :         if (bStop)
    3015             :         {
    3016           1 :             break;
    3017             :         }
    3018             :     }
    3019             : 
    3020         280 :     return true;
    3021             : }
    3022             : 
    3023             : /************************************************************************/
    3024             : /*                           GetSpatialRef()                            */
    3025             : /************************************************************************/
    3026             : 
    3027          25 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
    3028             : {
    3029          25 :     if (GetLayerCount())
    3030           3 :         return GDALDataset::GetSpatialRef();
    3031          22 :     return GetSpatialRefRasterOnly();
    3032             : }
    3033             : 
    3034             : /************************************************************************/
    3035             : /*                      GetSpatialRefRasterOnly()                       */
    3036             : /************************************************************************/
    3037             : 
    3038             : const OGRSpatialReference *
    3039          23 : GDALGeoPackageDataset::GetSpatialRefRasterOnly() const
    3040             : 
    3041             : {
    3042          23 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3043             : }
    3044             : 
    3045             : /************************************************************************/
    3046             : /*                           SetSpatialRef()                            */
    3047             : /************************************************************************/
    3048             : 
    3049         158 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3050             : {
    3051         158 :     if (nBands == 0)
    3052             :     {
    3053           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3054             :                  "SetProjection() not supported on a dataset with 0 band");
    3055           1 :         return CE_Failure;
    3056             :     }
    3057         157 :     if (eAccess != GA_Update)
    3058             :     {
    3059           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3060             :                  "SetProjection() not supported on read-only dataset");
    3061           1 :         return CE_Failure;
    3062             :     }
    3063             : 
    3064         156 :     const int nSRID = GetSrsId(poSRS);
    3065         312 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3066         156 :     if (poTS && nSRID != poTS->nEPSGCode)
    3067             :     {
    3068           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3069             :                  "Projection should be EPSG:%d for %s tiling scheme",
    3070           1 :                  poTS->nEPSGCode, m_osTilingScheme.c_str());
    3071           1 :         return CE_Failure;
    3072             :     }
    3073             : 
    3074         155 :     m_nSRID = nSRID;
    3075         155 :     m_oSRS.Clear();
    3076         155 :     if (poSRS)
    3077         154 :         m_oSRS = *poSRS;
    3078             : 
    3079         155 :     if (m_bRecordInsertedInGPKGContent)
    3080             :     {
    3081         124 :         char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
    3082             :                                        "WHERE lower(table_name) = lower('%q')",
    3083             :                                        m_nSRID, m_osRasterTable.c_str());
    3084         124 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3085         124 :         sqlite3_free(pszSQL);
    3086         124 :         if (eErr != OGRERR_NONE)
    3087           0 :             return CE_Failure;
    3088             : 
    3089         124 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
    3090             :                                  "WHERE lower(table_name) = lower('%q')",
    3091             :                                  m_nSRID, m_osRasterTable.c_str());
    3092         124 :         eErr = SQLCommand(hDB, pszSQL);
    3093         124 :         sqlite3_free(pszSQL);
    3094         124 :         if (eErr != OGRERR_NONE)
    3095           0 :             return CE_Failure;
    3096             :     }
    3097             : 
    3098         155 :     return CE_None;
    3099             : }
    3100             : 
    3101             : /************************************************************************/
    3102             : /*                          GetGeoTransform()                           */
    3103             : /************************************************************************/
    3104             : 
    3105          35 : CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform &gt) const
    3106             : {
    3107          35 :     gt = m_gt;
    3108          35 :     if (!m_bGeoTransformValid)
    3109           2 :         return CE_Failure;
    3110             :     else
    3111          33 :         return CE_None;
    3112             : }
    3113             : 
    3114             : /************************************************************************/
    3115             : /*                          SetGeoTransform()                           */
    3116             : /************************************************************************/
    3117             : 
    3118         198 : CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform &gt)
    3119             : {
    3120         198 :     if (nBands == 0)
    3121             :     {
    3122           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3123             :                  "SetGeoTransform() not supported on a dataset with 0 band");
    3124           2 :         return CE_Failure;
    3125             :     }
    3126         196 :     if (eAccess != GA_Update)
    3127             :     {
    3128           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3129             :                  "SetGeoTransform() not supported on read-only dataset");
    3130           1 :         return CE_Failure;
    3131             :     }
    3132         195 :     if (m_bGeoTransformValid)
    3133             :     {
    3134           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3135             :                  "Cannot modify geotransform once set");
    3136           1 :         return CE_Failure;
    3137             :     }
    3138         194 :     if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
    3139             :     {
    3140           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3141             :                  "Only north-up non rotated geotransform supported");
    3142           0 :         return CE_Failure;
    3143             :     }
    3144             : 
    3145         194 :     if (m_nZoomLevel < 0)
    3146             :     {
    3147         193 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3148         193 :         if (poTS)
    3149             :         {
    3150          20 :             double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3151          20 :             double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3152         199 :             for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
    3153         179 :                  m_nZoomLevel++)
    3154             :             {
    3155         198 :                 double dfExpectedPixelXSize =
    3156         198 :                     dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
    3157         198 :                 double dfExpectedPixelYSize =
    3158         198 :                     dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
    3159         198 :                 if (fabs(gt[1] - dfExpectedPixelXSize) <
    3160         217 :                         1e-8 * dfExpectedPixelXSize &&
    3161          19 :                     fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
    3162          19 :                         1e-8 * dfExpectedPixelYSize)
    3163             :                 {
    3164          19 :                     break;
    3165             :                 }
    3166             :             }
    3167          20 :             if (m_nZoomLevel == MAX_ZOOM_LEVEL)
    3168             :             {
    3169           1 :                 m_nZoomLevel = -1;
    3170           1 :                 CPLError(
    3171             :                     CE_Failure, CPLE_NotSupported,
    3172             :                     "Could not find an appropriate zoom level of %s tiling "
    3173             :                     "scheme that matches raster pixel size",
    3174             :                     m_osTilingScheme.c_str());
    3175           1 :                 return CE_Failure;
    3176             :             }
    3177             :         }
    3178             :     }
    3179             : 
    3180         193 :     m_gt = gt;
    3181         193 :     m_bGeoTransformValid = true;
    3182             : 
    3183         193 :     return FinalizeRasterRegistration();
    3184             : }
    3185             : 
    3186             : /************************************************************************/
    3187             : /*                     FinalizeRasterRegistration()                     */
    3188             : /************************************************************************/
    3189             : 
    3190         193 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3191             : {
    3192             :     OGRErr eErr;
    3193             : 
    3194         193 :     m_dfTMSMinX = m_gt[0];
    3195         193 :     m_dfTMSMaxY = m_gt[3];
    3196             : 
    3197             :     int nTileWidth, nTileHeight;
    3198         193 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3199             : 
    3200         193 :     if (m_nZoomLevel < 0)
    3201             :     {
    3202         173 :         m_nZoomLevel = 0;
    3203         252 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3204         173 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3205          79 :             m_nZoomLevel++;
    3206             :     }
    3207             : 
    3208         193 :     double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
    3209         193 :     double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
    3210             :     int nTileXCountZoomLevel0 =
    3211         193 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3212             :     int nTileYCountZoomLevel0 =
    3213         193 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3214             : 
    3215         386 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3216         193 :     if (poTS)
    3217             :     {
    3218          20 :         CPLAssert(m_nZoomLevel >= 0);
    3219          20 :         m_dfTMSMinX = poTS->dfMinX;
    3220          20 :         m_dfTMSMaxY = poTS->dfMaxY;
    3221          20 :         dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3222          20 :         dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3223          20 :         nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
    3224          20 :         nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
    3225             :     }
    3226         193 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3227         193 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3228             : 
    3229         193 :     if (!ComputeTileAndPixelShifts())
    3230             :     {
    3231           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    3232             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3233           1 :         return CE_Failure;
    3234             :     }
    3235             : 
    3236         192 :     if (!AllocCachedTiles())
    3237             :     {
    3238           0 :         return CE_Failure;
    3239             :     }
    3240             : 
    3241         192 :     double dfGDALMinX = m_gt[0];
    3242         192 :     double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
    3243         192 :     double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
    3244         192 :     double dfGDALMaxY = m_gt[3];
    3245             : 
    3246         192 :     if (SoftStartTransaction() != OGRERR_NONE)
    3247           0 :         return CE_Failure;
    3248             : 
    3249             :     const char *pszCurrentDate =
    3250         192 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3251             :     CPLString osInsertGpkgContentsFormatting(
    3252             :         "INSERT INTO gpkg_contents "
    3253             :         "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
    3254             :         "last_change,srs_id) VALUES "
    3255         384 :         "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
    3256         192 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3257         192 :     osInsertGpkgContentsFormatting += ",%d)";
    3258         384 :     char *pszSQL = sqlite3_mprintf(
    3259             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3260         192 :         (m_eDT == GDT_UInt8) ? "tiles" : "2d-gridded-coverage",
    3261             :         m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
    3262             :         dfGDALMaxX, dfGDALMaxY,
    3263             :         pszCurrentDate ? pszCurrentDate
    3264             :                        : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
    3265             :         m_nSRID);
    3266             : 
    3267         192 :     eErr = SQLCommand(hDB, pszSQL);
    3268         192 :     sqlite3_free(pszSQL);
    3269         192 :     if (eErr != OGRERR_NONE)
    3270             :     {
    3271           8 :         SoftRollbackTransaction();
    3272           8 :         return CE_Failure;
    3273             :     }
    3274             : 
    3275         184 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3276             :                                          dfPixelXSizeZoomLevel0;
    3277         184 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3278             :                                          dfPixelYSizeZoomLevel0;
    3279             : 
    3280             :     pszSQL =
    3281         184 :         sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
    3282             :                         "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
    3283             :                         "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
    3284             :                         m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
    3285             :                         dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
    3286         184 :     eErr = SQLCommand(hDB, pszSQL);
    3287         184 :     sqlite3_free(pszSQL);
    3288         184 :     if (eErr != OGRERR_NONE)
    3289             :     {
    3290           0 :         SoftRollbackTransaction();
    3291           0 :         return CE_Failure;
    3292             :     }
    3293             : 
    3294         184 :     m_apoOverviewDS.resize(m_nZoomLevel);
    3295             : 
    3296         608 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3297             :     {
    3298         424 :         double dfPixelXSizeZoomLevel = 0.0;
    3299         424 :         double dfPixelYSizeZoomLevel = 0.0;
    3300         424 :         int nTileMatrixWidth = 0;
    3301         424 :         int nTileMatrixHeight = 0;
    3302         424 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3303             :         {
    3304         243 :             dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
    3305         243 :             dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
    3306             :         }
    3307             :         else
    3308             :         {
    3309         181 :             dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
    3310         181 :             dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
    3311             :         }
    3312         424 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3313         424 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3314             : 
    3315         424 :         pszSQL = sqlite3_mprintf(
    3316             :             "INSERT INTO gpkg_tile_matrix "
    3317             :             "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
    3318             :             "height,pixel_x_size,pixel_y_size) VALUES "
    3319             :             "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3320             :             m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
    3321             :             nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
    3322             :             dfPixelYSizeZoomLevel);
    3323         424 :         eErr = SQLCommand(hDB, pszSQL);
    3324         424 :         sqlite3_free(pszSQL);
    3325         424 :         if (eErr != OGRERR_NONE)
    3326             :         {
    3327           0 :             SoftRollbackTransaction();
    3328           0 :             return CE_Failure;
    3329             :         }
    3330             : 
    3331         424 :         if (i < m_nZoomLevel)
    3332             :         {
    3333         480 :             auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3334         240 :             poOvrDS->ShareLockWithParentDataset(this);
    3335         240 :             poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
    3336             :                                 m_dfTMSMaxY, dfPixelXSizeZoomLevel,
    3337             :                                 dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
    3338             :                                 nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
    3339             :                                 dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    3340             : 
    3341         240 :             m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
    3342             :         }
    3343             :     }
    3344             : 
    3345         184 :     if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
    3346             :     {
    3347          40 :         eErr = SQLCommand(
    3348             :             hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
    3349          40 :         m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
    3350          40 :         if (eErr != OGRERR_NONE)
    3351             :         {
    3352           0 :             SoftRollbackTransaction();
    3353           0 :             return CE_Failure;
    3354             :         }
    3355             :     }
    3356             : 
    3357         184 :     SoftCommitTransaction();
    3358             : 
    3359         184 :     m_apoOverviewDS.resize(m_nZoomLevel);
    3360         184 :     m_bRecordInsertedInGPKGContent = true;
    3361             : 
    3362         184 :     return CE_None;
    3363             : }
    3364             : 
    3365             : /************************************************************************/
    3366             : /*                             FlushCache()                             */
    3367             : /************************************************************************/
    3368             : 
    3369        3190 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3370             : {
    3371        3190 :     if (m_bInFlushCache)
    3372           0 :         return CE_None;
    3373             : 
    3374        3190 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3375             :     {
    3376        3187 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3377             :     }
    3378             : 
    3379        3190 :     if (m_bRemoveOGREmptyTable)
    3380             :     {
    3381         916 :         m_bRemoveOGREmptyTable = false;
    3382         916 :         RemoveOGREmptyTable();
    3383             :     }
    3384             : 
    3385        3190 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3386             : 
    3387        3190 :     FlushMetadata();
    3388             : 
    3389        3190 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3390             :     {
    3391             :         // Needed again as above IFlushCacheWithErrCode()
    3392             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3393             :         // which modifies metadata
    3394        3190 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3395             :     }
    3396             : 
    3397        3190 :     return eErr;
    3398             : }
    3399             : 
    3400        5459 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3401             : 
    3402             : {
    3403        5459 :     if (m_bInFlushCache)
    3404        2202 :         return CE_None;
    3405        3257 :     m_bInFlushCache = true;
    3406        3257 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3407             :     {
    3408             :         // Clean-up metadata that will go to PAM by removing items that
    3409             :         // are reconstructed.
    3410        2394 :         CPLStringList aosMD;
    3411        1850 :         for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
    3412             :              ++papszIter)
    3413             :         {
    3414         653 :             char *pszKey = nullptr;
    3415         653 :             CPLParseNameValue(*papszIter, &pszKey);
    3416        1306 :             if (pszKey &&
    3417         653 :                 (EQUAL(pszKey, "AREA_OR_POINT") ||
    3418         499 :                  EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
    3419         267 :                  EQUAL(pszKey, "ZOOM_LEVEL") ||
    3420         683 :                  STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
    3421             :             {
    3422             :                 // remove it
    3423             :             }
    3424             :             else
    3425             :             {
    3426          30 :                 aosMD.AddString(*papszIter);
    3427             :             }
    3428         653 :             CPLFree(pszKey);
    3429             :         }
    3430        1197 :         oMDMD.SetMetadata(aosMD.List());
    3431        1197 :         oMDMD.SetMetadata(nullptr, GDAL_MDD_IMAGE_STRUCTURE);
    3432             : 
    3433        2394 :         GDALPamDataset::FlushCache(bAtClosing);
    3434             :     }
    3435             :     else
    3436             :     {
    3437             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3438        2060 :         GDALDataset::FlushCache(bAtClosing);
    3439             :     }
    3440             : 
    3441        7807 :     for (auto &poLayer : m_apoLayers)
    3442             :     {
    3443        4550 :         poLayer->RunDeferredCreationIfNecessary();
    3444        4550 :         poLayer->CreateSpatialIndexIfNecessary();
    3445             :     }
    3446             : 
    3447             :     // Update raster table last_change column in gpkg_contents if needed
    3448        3257 :     if (m_bHasModifiedTiles)
    3449             :     {
    3450         546 :         for (int i = 1; i <= nBands; ++i)
    3451             :         {
    3452             :             auto poBand =
    3453         362 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3454         362 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3455             :             {
    3456         348 :                 poBand->InvalidateStatistics();
    3457         348 :                 if (psPam && psPam->pszPamFilename)
    3458         348 :                     VSIUnlink(psPam->pszPamFilename);
    3459             :             }
    3460             :         }
    3461             : 
    3462         184 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3463             : 
    3464         184 :         m_bHasModifiedTiles = false;
    3465             :     }
    3466             : 
    3467        3257 :     CPLErr eErr = FlushTiles();
    3468             : 
    3469        3257 :     m_bInFlushCache = false;
    3470        3257 :     return eErr;
    3471             : }
    3472             : 
    3473             : /************************************************************************/
    3474             : /*                      GetCurrentDateEscapedSQL()                      */
    3475             : /************************************************************************/
    3476             : 
    3477        2495 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3478             : {
    3479             :     const char *pszCurrentDate =
    3480        2495 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3481        2495 :     if (pszCurrentDate)
    3482          14 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3483        2488 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3484             : }
    3485             : 
    3486             : /************************************************************************/
    3487             : /*                    UpdateGpkgContentsLastChange()                    */
    3488             : /************************************************************************/
    3489             : 
    3490             : OGRErr
    3491        1057 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3492             : {
    3493             :     char *pszSQL =
    3494        1057 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3495             :                         "last_change = %s "
    3496             :                         "WHERE lower(table_name) = lower('%q')",
    3497        2114 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3498        1057 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3499        1057 :     sqlite3_free(pszSQL);
    3500        1057 :     return eErr;
    3501             : }
    3502             : 
    3503             : /************************************************************************/
    3504             : /*                          IBuildOverviews()                           */
    3505             : /************************************************************************/
    3506             : 
    3507          20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
    3508             :     const char *pszResampling, int nOverviews, const int *panOverviewList,
    3509             :     int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
    3510             :     void *pProgressData, CSLConstList papszOptions)
    3511             : {
    3512          20 :     if (GetAccess() != GA_Update)
    3513             :     {
    3514           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3515             :                  "Overview building not supported on a database opened in "
    3516             :                  "read-only mode");
    3517           1 :         return CE_Failure;
    3518             :     }
    3519          19 :     if (m_poParentDS != nullptr)
    3520             :     {
    3521           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3522             :                  "Overview building not supported on overview dataset");
    3523           1 :         return CE_Failure;
    3524             :     }
    3525             : 
    3526          18 :     if (nOverviews == 0)
    3527             :     {
    3528           5 :         for (auto &poOvrDS : m_apoOverviewDS)
    3529           3 :             poOvrDS->FlushCache(false);
    3530             : 
    3531           2 :         SoftStartTransaction();
    3532             : 
    3533           2 :         if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3534             :         {
    3535           1 :             char *pszSQL = sqlite3_mprintf(
    3536             :                 "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
    3537             :                 "(SELECT y.id FROM \"%w\" x "
    3538             :                 "JOIN gpkg_2d_gridded_tile_ancillary y "
    3539             :                 "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
    3540             :                 "x.zoom_level < %d)",
    3541             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
    3542           1 :             OGRErr eErr = SQLCommand(hDB, pszSQL);
    3543           1 :             sqlite3_free(pszSQL);
    3544           1 :             if (eErr != OGRERR_NONE)
    3545             :             {
    3546           0 :                 SoftRollbackTransaction();
    3547           0 :                 return CE_Failure;
    3548             :             }
    3549             :         }
    3550             : 
    3551             :         char *pszSQL =
    3552           2 :             sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
    3553             :                             m_osRasterTable.c_str(), m_nZoomLevel);
    3554           2 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3555           2 :         sqlite3_free(pszSQL);
    3556           2 :         if (eErr != OGRERR_NONE)
    3557             :         {
    3558           0 :             SoftRollbackTransaction();
    3559           0 :             return CE_Failure;
    3560             :         }
    3561             : 
    3562           2 :         SoftCommitTransaction();
    3563             : 
    3564           2 :         return CE_None;
    3565             :     }
    3566             : 
    3567          16 :     if (nBandsIn != nBands)
    3568             :     {
    3569           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3570             :                  "Generation of overviews in GPKG only"
    3571             :                  "supported when operating on all bands.");
    3572           0 :         return CE_Failure;
    3573             :     }
    3574             : 
    3575          16 :     if (m_apoOverviewDS.empty())
    3576             :     {
    3577           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3578             :                  "Image too small to support overviews");
    3579           0 :         return CE_Failure;
    3580             :     }
    3581             : 
    3582          16 :     FlushCache(false);
    3583          60 :     for (int i = 0; i < nOverviews; i++)
    3584             :     {
    3585          47 :         if (panOverviewList[i] < 2)
    3586             :         {
    3587           1 :             CPLError(CE_Failure, CPLE_IllegalArg,
    3588             :                      "Overview factor must be >= 2");
    3589           1 :             return CE_Failure;
    3590             :         }
    3591             : 
    3592          46 :         bool bFound = false;
    3593          46 :         int jCandidate = -1;
    3594          46 :         int nMaxOvFactor = 0;
    3595         196 :         for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
    3596             :         {
    3597         190 :             const auto poODS = m_apoOverviewDS[j].get();
    3598             :             const int nOvFactor =
    3599         190 :                 static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
    3600             : 
    3601         190 :             nMaxOvFactor = nOvFactor;
    3602             : 
    3603         190 :             if (nOvFactor == panOverviewList[i])
    3604             :             {
    3605          40 :                 bFound = true;
    3606          40 :                 break;
    3607             :             }
    3608             : 
    3609         150 :             if (jCandidate < 0 && nOvFactor > panOverviewList[i])
    3610           1 :                 jCandidate = j;
    3611             :         }
    3612             : 
    3613          46 :         if (!bFound)
    3614             :         {
    3615             :             /* Mostly for debug */
    3616           6 :             if (!CPLTestBool(CPLGetConfigOption(
    3617             :                     "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
    3618             :             {
    3619           2 :                 CPLString osOvrList;
    3620           4 :                 for (const auto &poODS : m_apoOverviewDS)
    3621             :                 {
    3622             :                     const int nOvFactor =
    3623           2 :                         static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
    3624             : 
    3625           2 :                     if (!osOvrList.empty())
    3626           0 :                         osOvrList += ' ';
    3627           2 :                     osOvrList += CPLSPrintf("%d", nOvFactor);
    3628             :                 }
    3629           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3630             :                          "Only overviews %s can be computed",
    3631             :                          osOvrList.c_str());
    3632           2 :                 return CE_Failure;
    3633             :             }
    3634             :             else
    3635             :             {
    3636           4 :                 int nOvFactor = panOverviewList[i];
    3637           4 :                 if (jCandidate < 0)
    3638           3 :                     jCandidate = static_cast<int>(m_apoOverviewDS.size());
    3639             : 
    3640           4 :                 int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
    3641           4 :                 int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
    3642           4 :                 if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
    3643           5 :                       nOvFactor == 2 * nMaxOvFactor) &&
    3644           1 :                     !m_bZoomOther)
    3645             :                 {
    3646           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3647             :                              "Use of overview factor %d causes gpkg_zoom_other "
    3648             :                              "extension to be needed",
    3649             :                              nOvFactor);
    3650           1 :                     RegisterZoomOtherExtension();
    3651           1 :                     m_bZoomOther = true;
    3652             :                 }
    3653             : 
    3654           4 :                 SoftStartTransaction();
    3655             : 
    3656           4 :                 CPLAssert(jCandidate > 0);
    3657             :                 const int nNewZoomLevel =
    3658           4 :                     m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
    3659             : 
    3660             :                 char *pszSQL;
    3661             :                 OGRErr eErr;
    3662          24 :                 for (int k = 0; k <= jCandidate; k++)
    3663             :                 {
    3664          60 :                     pszSQL = sqlite3_mprintf(
    3665             :                         "UPDATE gpkg_tile_matrix SET zoom_level = %d "
    3666             :                         "WHERE lower(table_name) = lower('%q') AND zoom_level "
    3667             :                         "= %d",
    3668          20 :                         m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
    3669          20 :                         m_nZoomLevel - k);
    3670          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3671          20 :                     sqlite3_free(pszSQL);
    3672          20 :                     if (eErr != OGRERR_NONE)
    3673             :                     {
    3674           0 :                         SoftRollbackTransaction();
    3675           0 :                         return CE_Failure;
    3676             :                     }
    3677             : 
    3678             :                     pszSQL =
    3679          20 :                         sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
    3680             :                                         "WHERE zoom_level = %d",
    3681             :                                         m_osRasterTable.c_str(),
    3682          20 :                                         m_nZoomLevel - k + 1, m_nZoomLevel - k);
    3683          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3684          20 :                     sqlite3_free(pszSQL);
    3685          20 :                     if (eErr != OGRERR_NONE)
    3686             :                     {
    3687           0 :                         SoftRollbackTransaction();
    3688           0 :                         return CE_Failure;
    3689             :                     }
    3690             :                 }
    3691             : 
    3692           4 :                 double dfGDALMinX = m_gt[0];
    3693           4 :                 double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
    3694           4 :                 double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
    3695           4 :                 double dfGDALMaxY = m_gt[3];
    3696           4 :                 double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
    3697           4 :                 double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
    3698             :                 int nTileWidth, nTileHeight;
    3699           4 :                 GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3700           4 :                 int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
    3701           4 :                 int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
    3702           4 :                 pszSQL = sqlite3_mprintf(
    3703             :                     "INSERT INTO gpkg_tile_matrix "
    3704             :                     "(table_name,zoom_level,matrix_width,matrix_height,tile_"
    3705             :                     "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
    3706             :                     "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3707             :                     m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
    3708             :                     nTileMatrixHeight, nTileWidth, nTileHeight,
    3709             :                     dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
    3710           4 :                 eErr = SQLCommand(hDB, pszSQL);
    3711           4 :                 sqlite3_free(pszSQL);
    3712           4 :                 if (eErr != OGRERR_NONE)
    3713             :                 {
    3714           0 :                     SoftRollbackTransaction();
    3715           0 :                     return CE_Failure;
    3716             :                 }
    3717             : 
    3718           4 :                 SoftCommitTransaction();
    3719             : 
    3720           4 :                 m_nZoomLevel++; /* this change our zoom level as well as
    3721             :                                    previous overviews */
    3722          20 :                 for (int k = 0; k < jCandidate; k++)
    3723          16 :                     m_apoOverviewDS[k]->m_nZoomLevel++;
    3724             : 
    3725           4 :                 auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
    3726           4 :                 poOvrDS->ShareLockWithParentDataset(this);
    3727           4 :                 poOvrDS->InitRaster(
    3728             :                     this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
    3729             :                     m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
    3730             :                     nTileWidth, nTileHeight, nTileMatrixWidth,
    3731             :                     nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
    3732             :                     dfGDALMaxY);
    3733           4 :                 m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
    3734           8 :                                        std::move(poOvrDS));
    3735             :             }
    3736             :         }
    3737             :     }
    3738             : 
    3739             :     GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
    3740          13 :         CPLCalloc(sizeof(GDALRasterBand **), nBands));
    3741          13 :     CPLErr eErr = CE_None;
    3742          49 :     for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
    3743             :     {
    3744          72 :         papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
    3745          36 :             CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
    3746          36 :         int iCurOverview = 0;
    3747         185 :         for (int i = 0; i < nOverviews; i++)
    3748             :         {
    3749         149 :             bool bFound = false;
    3750         724 :             for (const auto &poODS : m_apoOverviewDS)
    3751             :             {
    3752             :                 const int nOvFactor =
    3753         724 :                     static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
    3754             : 
    3755         724 :                 if (nOvFactor == panOverviewList[i])
    3756             :                 {
    3757         298 :                     papapoOverviewBands[iBand][iCurOverview] =
    3758         149 :                         poODS->GetRasterBand(iBand + 1);
    3759         149 :                     iCurOverview++;
    3760         149 :                     bFound = true;
    3761         149 :                     break;
    3762             :                 }
    3763             :             }
    3764         149 :             if (!bFound)
    3765             :             {
    3766           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3767             :                          "Could not find dataset corresponding to ov factor %d",
    3768           0 :                          panOverviewList[i]);
    3769           0 :                 eErr = CE_Failure;
    3770             :             }
    3771             :         }
    3772          36 :         if (eErr == CE_None)
    3773             :         {
    3774          36 :             CPLAssert(iCurOverview == nOverviews);
    3775             :         }
    3776             :     }
    3777             : 
    3778          13 :     if (eErr == CE_None)
    3779          13 :         eErr = GDALRegenerateOverviewsMultiBand(
    3780          13 :             nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
    3781             :             pfnProgress, pProgressData, papszOptions);
    3782             : 
    3783          49 :     for (int iBand = 0; iBand < nBands; iBand++)
    3784             :     {
    3785          36 :         CPLFree(papapoOverviewBands[iBand]);
    3786             :     }
    3787          13 :     CPLFree(papapoOverviewBands);
    3788             : 
    3789          13 :     return eErr;
    3790             : }
    3791             : 
    3792             : /************************************************************************/
    3793             : /*                            GetFileList()                             */
    3794             : /************************************************************************/
    3795             : 
    3796          38 : char **GDALGeoPackageDataset::GetFileList()
    3797             : {
    3798          38 :     TryLoadXML();
    3799          38 :     return GDALPamDataset::GetFileList();
    3800             : }
    3801             : 
    3802             : /************************************************************************/
    3803             : /*                       GetMetadataDomainList()                        */
    3804             : /************************************************************************/
    3805             : 
    3806          53 : char **GDALGeoPackageDataset::GetMetadataDomainList()
    3807             : {
    3808          53 :     GetMetadata();
    3809          53 :     if (!m_osRasterTable.empty())
    3810           5 :         GetMetadata("GEOPACKAGE");
    3811          53 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    3812          53 :                                    TRUE, GDAL_MDD_SUBDATASETS, nullptr);
    3813             : }
    3814             : 
    3815             : /************************************************************************/
    3816             : /*                        CheckMetadataDomain()                         */
    3817             : /************************************************************************/
    3818             : 
    3819        6875 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3820             : {
    3821        7063 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3822         188 :         m_osRasterTable.empty())
    3823             :     {
    3824           4 :         CPLError(
    3825             :             CE_Warning, CPLE_IllegalArg,
    3826             :             "Using GEOPACKAGE for a non-raster geopackage is not supported. "
    3827             :             "Using default domain instead");
    3828           4 :         return nullptr;
    3829             :     }
    3830        6871 :     return pszDomain;
    3831             : }
    3832             : 
    3833             : /************************************************************************/
    3834             : /*                         HasMetadataTables()                          */
    3835             : /************************************************************************/
    3836             : 
    3837        6542 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3838             : {
    3839        6542 :     if (m_nHasMetadataTables < 0)
    3840             :     {
    3841             :         const int nCount =
    3842        2483 :             SQLGetInteger(hDB,
    3843             :                           "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
    3844             :                           "('gpkg_metadata', 'gpkg_metadata_reference') "
    3845             :                           "AND type IN ('table', 'view')",
    3846             :                           nullptr);
    3847        2483 :         m_nHasMetadataTables = nCount == 2;
    3848             :     }
    3849        6542 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3850             : }
    3851             : 
    3852             : /************************************************************************/
    3853             : /*                        HasDataColumnsTable()                         */
    3854             : /************************************************************************/
    3855             : 
    3856        1424 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3857             : {
    3858        2848 :     const int nCount = SQLGetInteger(
    3859        1424 :         hDB,
    3860             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    3861             :         "AND type IN ('table', 'view')",
    3862             :         nullptr);
    3863        1424 :     return nCount == 1;
    3864             : }
    3865             : 
    3866             : /************************************************************************/
    3867             : /*                   HasDataColumnConstraintsTable()                    */
    3868             : /************************************************************************/
    3869             : 
    3870         168 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
    3871             : {
    3872         168 :     const int nCount = SQLGetInteger(hDB,
    3873             :                                      "SELECT 1 FROM sqlite_master WHERE name = "
    3874             :                                      "'gpkg_data_column_constraints'"
    3875             :                                      "AND type IN ('table', 'view')",
    3876             :                                      nullptr);
    3877         168 :     return nCount == 1;
    3878             : }
    3879             : 
    3880             : /************************************************************************/
    3881             : /*               HasDataColumnConstraintsTableGPKG_1_0()                */
    3882             : /************************************************************************/
    3883             : 
    3884         111 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
    3885             : {
    3886         111 :     if (m_nApplicationId != GP10_APPLICATION_ID)
    3887         109 :         return false;
    3888             :     // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
    3889             :     // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
    3890           2 :     bool bRet = false;
    3891           2 :     sqlite3_stmt *hSQLStmt = nullptr;
    3892           2 :     int rc = sqlite3_prepare_v2(hDB,
    3893             :                                 "SELECT minIsInclusive, maxIsInclusive FROM "
    3894             :                                 "gpkg_data_column_constraints",
    3895             :                                 -1, &hSQLStmt, nullptr);
    3896           2 :     if (rc == SQLITE_OK)
    3897             :     {
    3898           2 :         bRet = true;
    3899           2 :         sqlite3_finalize(hSQLStmt);
    3900             :     }
    3901           2 :     return bRet;
    3902             : }
    3903             : 
    3904             : /************************************************************************/
    3905             : /*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
    3906             : /************************************************************************/
    3907             : 
    3908          53 : bool GDALGeoPackageDataset::
    3909             :     CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
    3910             : {
    3911          53 :     if (!HasDataColumnsTable())
    3912             :     {
    3913             :         // Geopackage < 1.3 had
    3914             :         // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
    3915             :         // gpkg_contents(table_name) instead of the unique constraint.
    3916          13 :         if (OGRERR_NONE !=
    3917          13 :             SQLCommand(
    3918             :                 GetDB(),
    3919             :                 "CREATE TABLE gpkg_data_columns ("
    3920             :                 "table_name TEXT NOT NULL,"
    3921             :                 "column_name TEXT NOT NULL,"
    3922             :                 "name TEXT,"
    3923             :                 "title TEXT,"
    3924             :                 "description TEXT,"
    3925             :                 "mime_type TEXT,"
    3926             :                 "constraint_name TEXT,"
    3927             :                 "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
    3928             :                 "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
    3929             :         {
    3930           0 :             return false;
    3931             :         }
    3932             :     }
    3933          53 :     if (!HasDataColumnConstraintsTable())
    3934             :     {
    3935          28 :         const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    3936          14 :                                            ? "min_is_inclusive"
    3937             :                                            : "minIsInclusive";
    3938          28 :         const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    3939          14 :                                            ? "max_is_inclusive"
    3940             :                                            : "maxIsInclusive";
    3941             : 
    3942             :         const std::string osSQL(
    3943             :             CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
    3944             :                        "constraint_name TEXT NOT NULL,"
    3945             :                        "constraint_type TEXT NOT NULL,"
    3946             :                        "value TEXT,"
    3947             :                        "min NUMERIC,"
    3948             :                        "%s BOOLEAN,"
    3949             :                        "max NUMERIC,"
    3950             :                        "%s BOOLEAN,"
    3951             :                        "description TEXT,"
    3952             :                        "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
    3953             :                        "constraint_type, value));",
    3954          14 :                        min_is_inclusive, max_is_inclusive));
    3955          14 :         if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
    3956             :         {
    3957           0 :             return false;
    3958             :         }
    3959             :     }
    3960          53 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    3961             :     {
    3962           0 :         return false;
    3963             :     }
    3964          53 :     if (SQLGetInteger(GetDB(),
    3965             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    3966             :                       "table_name = 'gpkg_data_columns'",
    3967          53 :                       nullptr) != 1)
    3968             :     {
    3969          14 :         if (OGRERR_NONE !=
    3970          14 :             SQLCommand(
    3971             :                 GetDB(),
    3972             :                 "INSERT INTO gpkg_extensions "
    3973             :                 "(table_name,column_name,extension_name,definition,scope) "
    3974             :                 "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
    3975             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    3976             :                 "'read-write')"))
    3977             :         {
    3978           0 :             return false;
    3979             :         }
    3980             :     }
    3981          53 :     if (SQLGetInteger(GetDB(),
    3982             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    3983             :                       "table_name = 'gpkg_data_column_constraints'",
    3984          53 :                       nullptr) != 1)
    3985             :     {
    3986          14 :         if (OGRERR_NONE !=
    3987          14 :             SQLCommand(
    3988             :                 GetDB(),
    3989             :                 "INSERT INTO gpkg_extensions "
    3990             :                 "(table_name,column_name,extension_name,definition,scope) "
    3991             :                 "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
    3992             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    3993             :                 "'read-write')"))
    3994             :         {
    3995           0 :             return false;
    3996             :         }
    3997             :     }
    3998             : 
    3999          53 :     return true;
    4000             : }
    4001             : 
    4002             : /************************************************************************/
    4003             : /*                      HasGpkgextRelationsTable()                      */
    4004             : /************************************************************************/
    4005             : 
    4006        1464 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4007             : {
    4008        2928 :     const int nCount = SQLGetInteger(
    4009        1464 :         hDB,
    4010             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4011             :         "AND type IN ('table', 'view')",
    4012             :         nullptr);
    4013        1464 :     return nCount == 1;
    4014             : }
    4015             : 
    4016             : /************************************************************************/
    4017             : /*                  CreateRelationsTableIfNecessary()                   */
    4018             : /************************************************************************/
    4019             : 
    4020          11 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
    4021             : {
    4022          11 :     if (HasGpkgextRelationsTable())
    4023             :     {
    4024           6 :         return true;
    4025             :     }
    4026             : 
    4027           5 :     if (OGRERR_NONE !=
    4028           5 :         SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
    4029             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    4030             :                             "base_table_name TEXT NOT NULL,"
    4031             :                             "base_primary_column TEXT NOT NULL DEFAULT 'id',"
    4032             :                             "related_table_name TEXT NOT NULL,"
    4033             :                             "related_primary_column TEXT NOT NULL DEFAULT 'id',"
    4034             :                             "relation_name TEXT NOT NULL,"
    4035             :                             "mapping_table_name TEXT NOT NULL UNIQUE);"))
    4036             :     {
    4037           0 :         return false;
    4038             :     }
    4039             : 
    4040           5 :     return true;
    4041             : }
    4042             : 
    4043             : /************************************************************************/
    4044             : /*                         HasQGISLayerStyles()                         */
    4045             : /************************************************************************/
    4046             : 
    4047          11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
    4048             : {
    4049             :     // QGIS layer_styles extension:
    4050             :     // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
    4051          11 :     bool bRet = false;
    4052             :     const int nCount =
    4053          11 :         SQLGetInteger(hDB,
    4054             :                       "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
    4055             :                       "AND type = 'table'",
    4056             :                       nullptr);
    4057          11 :     if (nCount == 1)
    4058             :     {
    4059           1 :         sqlite3_stmt *hSQLStmt = nullptr;
    4060           2 :         int rc = sqlite3_prepare_v2(
    4061           1 :             hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
    4062             :             &hSQLStmt, nullptr);
    4063           1 :         if (rc == SQLITE_OK)
    4064             :         {
    4065           1 :             bRet = true;
    4066           1 :             sqlite3_finalize(hSQLStmt);
    4067             :         }
    4068             :     }
    4069          11 :     return bRet;
    4070             : }
    4071             : 
    4072             : /************************************************************************/
    4073             : /*                            GetMetadata()                             */
    4074             : /************************************************************************/
    4075             : 
    4076        4509 : CSLConstList GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4077             : 
    4078             : {
    4079        4509 :     pszDomain = CheckMetadataDomain(pszDomain);
    4080        4509 :     if (pszDomain != nullptr && EQUAL(pszDomain, GDAL_MDD_SUBDATASETS))
    4081          83 :         return m_aosSubDatasets.List();
    4082             : 
    4083        4426 :     if (m_bHasReadMetadataFromStorage)
    4084        2013 :         return GDALPamDataset::GetMetadata(pszDomain);
    4085             : 
    4086        2413 :     m_bHasReadMetadataFromStorage = true;
    4087             : 
    4088        2413 :     TryLoadXML();
    4089             : 
    4090        2413 :     if (!HasMetadataTables())
    4091        1823 :         return GDALPamDataset::GetMetadata(pszDomain);
    4092             : 
    4093         590 :     char *pszSQL = nullptr;
    4094         590 :     if (!m_osRasterTable.empty())
    4095             :     {
    4096         178 :         pszSQL = sqlite3_mprintf(
    4097             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4098             :             "mdr.reference_scope FROM gpkg_metadata md "
    4099             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4100             :             "WHERE "
    4101             :             "(mdr.reference_scope = 'geopackage' OR "
    4102             :             "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
    4103             :             "lower('%q'))) ORDER BY md.id "
    4104             :             "LIMIT 1000",  // to avoid denial of service
    4105             :             m_osRasterTable.c_str());
    4106             :     }
    4107             :     else
    4108             :     {
    4109         412 :         pszSQL = sqlite3_mprintf(
    4110             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4111             :             "mdr.reference_scope FROM gpkg_metadata md "
    4112             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4113             :             "WHERE "
    4114             :             "mdr.reference_scope = 'geopackage' ORDER BY md.id "
    4115             :             "LIMIT 1000"  // to avoid denial of service
    4116             :         );
    4117             :     }
    4118             : 
    4119        1180 :     auto oResult = SQLQuery(hDB, pszSQL);
    4120         590 :     sqlite3_free(pszSQL);
    4121         590 :     if (!oResult)
    4122             :     {
    4123           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4124             :     }
    4125             : 
    4126         590 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4127             : 
    4128             :     /* GDAL metadata */
    4129         788 :     for (int i = 0; i < oResult->RowCount(); i++)
    4130             :     {
    4131         198 :         const char *pszMetadata = oResult->GetValue(0, i);
    4132         198 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4133         198 :         const char *pszMimeType = oResult->GetValue(2, i);
    4134         198 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4135         198 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4136         198 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4137         182 :             EQUAL(pszMimeType, "text/xml"))
    4138             :         {
    4139         182 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4140         182 :             if (psXMLNode)
    4141             :             {
    4142         364 :                 GDALMultiDomainMetadata oLocalMDMD;
    4143         182 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4144         349 :                 if (!m_osRasterTable.empty() &&
    4145         167 :                     EQUAL(pszReferenceScope, "geopackage"))
    4146             :                 {
    4147           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4148             :                 }
    4149             :                 else
    4150             :                 {
    4151             :                     papszMetadata =
    4152         176 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4153         176 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4154         176 :                     CSLConstList papszIter = papszDomainList;
    4155         473 :                     while (papszIter && *papszIter)
    4156             :                     {
    4157         297 :                         if (EQUAL(*papszIter, GDAL_MDD_IMAGE_STRUCTURE))
    4158             :                         {
    4159             :                             CSLConstList papszMD =
    4160         134 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4161             :                             const char *pszBAND_COUNT =
    4162         134 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4163         134 :                             if (pszBAND_COUNT)
    4164         132 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4165             : 
    4166             :                             const char *pszCOLOR_TABLE =
    4167         134 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4168         134 :                             if (pszCOLOR_TABLE)
    4169             :                             {
    4170             :                                 const CPLStringList aosTokens(
    4171             :                                     CSLTokenizeString2(pszCOLOR_TABLE, "{,",
    4172          26 :                                                        0));
    4173          13 :                                 if ((aosTokens.size() % 4) == 0)
    4174             :                                 {
    4175          13 :                                     const int nColors = aosTokens.size() / 4;
    4176             :                                     m_poCTFromMetadata =
    4177          13 :                                         std::make_unique<GDALColorTable>();
    4178        3341 :                                     for (int iColor = 0; iColor < nColors;
    4179             :                                          ++iColor)
    4180             :                                     {
    4181             :                                         GDALColorEntry sEntry;
    4182        3328 :                                         sEntry.c1 = static_cast<short>(
    4183        3328 :                                             atoi(aosTokens[4 * iColor + 0]));
    4184        3328 :                                         sEntry.c2 = static_cast<short>(
    4185        3328 :                                             atoi(aosTokens[4 * iColor + 1]));
    4186        3328 :                                         sEntry.c3 = static_cast<short>(
    4187        3328 :                                             atoi(aosTokens[4 * iColor + 2]));
    4188        3328 :                                         sEntry.c4 = static_cast<short>(
    4189        3328 :                                             atoi(aosTokens[4 * iColor + 3]));
    4190        3328 :                                         m_poCTFromMetadata->SetColorEntry(
    4191             :                                             iColor, &sEntry);
    4192             :                                     }
    4193             :                                 }
    4194             :                             }
    4195             : 
    4196             :                             const char *pszTILE_FORMAT =
    4197         134 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4198         134 :                             if (pszTILE_FORMAT)
    4199             :                             {
    4200           8 :                                 m_osTFFromMetadata = pszTILE_FORMAT;
    4201           8 :                                 oMDMD.SetMetadataItem("TILE_FORMAT",
    4202             :                                                       pszTILE_FORMAT,
    4203             :                                                       GDAL_MDD_IMAGE_STRUCTURE);
    4204             :                             }
    4205             : 
    4206             :                             const char *pszNodataValue =
    4207         134 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4208         134 :                             if (pszNodataValue)
    4209             :                             {
    4210           2 :                                 m_osNodataValueFromMetadata = pszNodataValue;
    4211             :                             }
    4212             :                         }
    4213             : 
    4214         163 :                         else if (!EQUAL(*papszIter, "") &&
    4215          18 :                                  !STARTS_WITH(*papszIter, "BAND_"))
    4216             :                         {
    4217          12 :                             oMDMD.SetMetadata(
    4218           6 :                                 oLocalMDMD.GetMetadata(*papszIter), *papszIter);
    4219             :                         }
    4220         297 :                         papszIter++;
    4221             :                     }
    4222             :                 }
    4223         182 :                 CPLDestroyXMLNode(psXMLNode);
    4224             :             }
    4225             :         }
    4226             :     }
    4227             : 
    4228         590 :     GDALPamDataset::SetMetadata(papszMetadata);
    4229         590 :     CSLDestroy(papszMetadata);
    4230         590 :     papszMetadata = nullptr;
    4231             : 
    4232             :     /* Add non-GDAL metadata now */
    4233         590 :     int nNonGDALMDILocal = 1;
    4234         590 :     int nNonGDALMDIGeopackage = 1;
    4235         788 :     for (int i = 0; i < oResult->RowCount(); i++)
    4236             :     {
    4237         198 :         const char *pszMetadata = oResult->GetValue(0, i);
    4238         198 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4239         198 :         const char *pszMimeType = oResult->GetValue(2, i);
    4240         198 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4241         198 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4242         198 :             pszMimeType == nullptr || pszReferenceScope == nullptr)
    4243             :         {
    4244             :             // should not happen as there are NOT NULL constraints
    4245             :             // But a database could lack such NOT NULL constraints or have
    4246             :             // large values that would cause a memory allocation failure.
    4247           0 :             continue;
    4248             :         }
    4249         198 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4250         198 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4251         182 :             EQUAL(pszMimeType, "text/xml"))
    4252         182 :             continue;
    4253             : 
    4254          16 :         if (!m_osRasterTable.empty() && bIsGPKGScope)
    4255             :         {
    4256           8 :             oMDMD.SetMetadataItem(
    4257             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
    4258             :                 pszMetadata, "GEOPACKAGE");
    4259           8 :             nNonGDALMDIGeopackage++;
    4260             :         }
    4261             :         /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
    4262             :         ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
    4263             :         {
    4264             :             char* apszMD[2];
    4265             :             apszMD[0] = (char*)pszMetadata;
    4266             :             apszMD[1] = NULL;
    4267             :             oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
    4268             :         }*/
    4269             :         else
    4270             :         {
    4271           8 :             oMDMD.SetMetadataItem(
    4272             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
    4273             :                 pszMetadata);
    4274           8 :             nNonGDALMDILocal++;
    4275             :         }
    4276             :     }
    4277             : 
    4278         590 :     return GDALPamDataset::GetMetadata(pszDomain);
    4279             : }
    4280             : 
    4281             : /************************************************************************/
    4282             : /*                           WriteMetadata()                            */
    4283             : /************************************************************************/
    4284             : 
    4285         880 : void GDALGeoPackageDataset::WriteMetadata(
    4286             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4287             :     const char *pszTableName)
    4288             : {
    4289         880 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4290         880 :     if (!HasMetadataTables())
    4291             :     {
    4292         673 :         if (bIsEmpty || !CreateMetadataTables())
    4293             :         {
    4294         312 :             CPLDestroyXMLNode(psXMLNode);
    4295         312 :             return;
    4296             :         }
    4297             :     }
    4298             : 
    4299         568 :     char *pszXML = nullptr;
    4300         568 :     if (!bIsEmpty)
    4301             :     {
    4302             :         CPLXMLNode *psMasterXMLNode =
    4303         411 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4304         411 :         psMasterXMLNode->psChild = psXMLNode;
    4305         411 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4306         411 :         CPLDestroyXMLNode(psMasterXMLNode);
    4307             :     }
    4308             :     // cppcheck-suppress uselessAssignmentPtrArg
    4309         568 :     psXMLNode = nullptr;
    4310             : 
    4311         568 :     char *pszSQL = nullptr;
    4312         568 :     if (pszTableName && pszTableName[0] != '\0')
    4313             :     {
    4314         419 :         pszSQL = sqlite3_mprintf(
    4315             :             "SELECT md.id FROM gpkg_metadata md "
    4316             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4317             :             "WHERE md.md_scope = 'dataset' AND "
    4318             :             "md.md_standard_uri='http://gdal.org' "
    4319             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
    4320             :             "lower(mdr.table_name) = lower('%q')",
    4321             :             pszTableName);
    4322             :     }
    4323             :     else
    4324             :     {
    4325         149 :         pszSQL = sqlite3_mprintf(
    4326             :             "SELECT md.id FROM gpkg_metadata md "
    4327             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4328             :             "WHERE md.md_scope = 'dataset' AND "
    4329             :             "md.md_standard_uri='http://gdal.org' "
    4330             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = "
    4331             :             "'geopackage'");
    4332             :     }
    4333             :     OGRErr err;
    4334         568 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4335         568 :     if (err != OGRERR_NONE)
    4336         535 :         mdId = -1;
    4337         568 :     sqlite3_free(pszSQL);
    4338             : 
    4339         568 :     if (bIsEmpty)
    4340             :     {
    4341         157 :         if (mdId >= 0)
    4342             :         {
    4343           6 :             SQLCommand(
    4344             :                 hDB,
    4345             :                 CPLSPrintf(
    4346             :                     "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
    4347             :                     mdId));
    4348           6 :             SQLCommand(
    4349             :                 hDB,
    4350             :                 CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
    4351             :         }
    4352             :     }
    4353             :     else
    4354             :     {
    4355         411 :         if (mdId >= 0)
    4356             :         {
    4357          27 :             pszSQL = sqlite3_mprintf(
    4358             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4359             :                 pszXML, mdId);
    4360             :         }
    4361             :         else
    4362             :         {
    4363             :             pszSQL =
    4364         384 :                 sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
    4365             :                                 "md_standard_uri, mime_type, metadata) VALUES "
    4366             :                                 "('dataset','http://gdal.org','text/xml','%q')",
    4367             :                                 pszXML);
    4368             :         }
    4369         411 :         SQLCommand(hDB, pszSQL);
    4370         411 :         sqlite3_free(pszSQL);
    4371             : 
    4372         411 :         CPLFree(pszXML);
    4373             : 
    4374         411 :         if (mdId < 0)
    4375             :         {
    4376         384 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4377         384 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4378             :             {
    4379         372 :                 pszSQL = sqlite3_mprintf(
    4380             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4381             :                     "table_name, timestamp, md_file_id) VALUES "
    4382             :                     "('table', '%q', %s, %d)",
    4383         744 :                     pszTableName, GetCurrentDateEscapedSQL().c_str(),
    4384             :                     static_cast<int>(nFID));
    4385             :             }
    4386             :             else
    4387             :             {
    4388          12 :                 pszSQL = sqlite3_mprintf(
    4389             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4390             :                     "timestamp, md_file_id) VALUES "
    4391             :                     "('geopackage', %s, %d)",
    4392          24 :                     GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
    4393             :             }
    4394             :         }
    4395             :         else
    4396             :         {
    4397          27 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4398             :                                      "timestamp = %s WHERE md_file_id = %d",
    4399          54 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4400             :         }
    4401         411 :         SQLCommand(hDB, pszSQL);
    4402         411 :         sqlite3_free(pszSQL);
    4403             :     }
    4404             : }
    4405             : 
    4406             : /************************************************************************/
    4407             : /*                        CreateMetadataTables()                        */
    4408             : /************************************************************************/
    4409             : 
    4410         380 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4411             : {
    4412             :     const bool bCreateTriggers =
    4413         380 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
    4414             : 
    4415             :     /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
    4416             :     CPLString osSQL = "CREATE TABLE gpkg_metadata ("
    4417             :                       "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
    4418             :                       "md_scope TEXT NOT NULL DEFAULT 'dataset',"
    4419             :                       "md_standard_uri TEXT NOT NULL,"
    4420             :                       "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
    4421             :                       "metadata TEXT NOT NULL DEFAULT ''"
    4422         760 :                       ")";
    4423             : 
    4424             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4425         380 :     const char *pszMetadataTriggers =
    4426             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
    4427             :         "BEFORE INSERT ON 'gpkg_metadata' "
    4428             :         "FOR EACH ROW BEGIN "
    4429             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
    4430             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4431             :         "collectionSession | series | dataset | featureType | feature | "
    4432             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4433             :         "taxonomy software | service | collectionHardware | "
    4434             :         "nonGeographicDataset | dimensionGroup') "
    4435             :         "WHERE NOT(NEW.md_scope IN "
    4436             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4437             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4438             :         "'catalogue','schema','taxonomy','software','service', "
    4439             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4440             :         "END; "
    4441             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
    4442             :         "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
    4443             :         "FOR EACH ROW BEGIN "
    4444             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
    4445             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4446             :         "collectionSession | series | dataset | featureType | feature | "
    4447             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4448             :         "taxonomy software | service | collectionHardware | "
    4449             :         "nonGeographicDataset | dimensionGroup') "
    4450             :         "WHERE NOT(NEW.md_scope IN "
    4451             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4452             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4453             :         "'catalogue','schema','taxonomy','software','service', "
    4454             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4455             :         "END";
    4456         380 :     if (bCreateTriggers)
    4457             :     {
    4458           0 :         osSQL += ";";
    4459           0 :         osSQL += pszMetadataTriggers;
    4460             :     }
    4461             : 
    4462             :     /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
    4463             :      * Table Definition SQL */
    4464             :     osSQL += ";"
    4465             :              "CREATE TABLE gpkg_metadata_reference ("
    4466             :              "reference_scope TEXT NOT NULL,"
    4467             :              "table_name TEXT,"
    4468             :              "column_name TEXT,"
    4469             :              "row_id_value INTEGER,"
    4470             :              "timestamp DATETIME NOT NULL DEFAULT "
    4471             :              "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    4472             :              "md_file_id INTEGER NOT NULL,"
    4473             :              "md_parent_id INTEGER,"
    4474             :              "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
    4475             :              "gpkg_metadata(id),"
    4476             :              "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
    4477             :              "gpkg_metadata(id)"
    4478         380 :              ")";
    4479             : 
    4480             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4481             :      * Definition SQL   */
    4482         380 :     const char *pszMetadataReferenceTriggers =
    4483             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
    4484             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4485             :         "FOR EACH ROW BEGIN "
    4486             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4487             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4488             :         "table\", \"column\", \"row\", \"row/col\"') "
    4489             :         "WHERE NOT NEW.reference_scope IN "
    4490             :         "('geopackage','table','column','row','row/col'); "
    4491             :         "END; "
    4492             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
    4493             :         "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
    4494             :         "FOR EACH ROW BEGIN "
    4495             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4496             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4497             :         "\"table\", \"column\", \"row\", \"row/col\"') "
    4498             :         "WHERE NOT NEW.reference_scope IN "
    4499             :         "('geopackage','table','column','row','row/col'); "
    4500             :         "END; "
    4501             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
    4502             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4503             :         "FOR EACH ROW BEGIN "
    4504             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4505             :         "violates constraint: column name must be NULL when reference_scope "
    4506             :         "is \"geopackage\", \"table\" or \"row\"') "
    4507             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4508             :         "AND NEW.column_name IS NOT NULL); "
    4509             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4510             :         "violates constraint: column name must be defined for the specified "
    4511             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4512             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4513             :         "AND NOT NEW.table_name IN ( "
    4514             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4515             :         "AND name = NEW.table_name "
    4516             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4517             :         "END; "
    4518             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
    4519             :         "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
    4520             :         "FOR EACH ROW BEGIN "
    4521             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4522             :         "violates constraint: column name must be NULL when reference_scope "
    4523             :         "is \"geopackage\", \"table\" or \"row\"') "
    4524             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4525             :         "AND NEW.column_name IS NOT NULL); "
    4526             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4527             :         "violates constraint: column name must be defined for the specified "
    4528             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4529             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4530             :         "AND NOT NEW.table_name IN ( "
    4531             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4532             :         "AND name = NEW.table_name "
    4533             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4534             :         "END; "
    4535             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
    4536             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4537             :         "FOR EACH ROW BEGIN "
    4538             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4539             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4540             :         "is \"geopackage\", \"table\" or \"column\"') "
    4541             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4542             :         "AND NEW.row_id_value IS NOT NULL; "
    4543             :         "END; "
    4544             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
    4545             :         "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
    4546             :         "FOR EACH ROW BEGIN "
    4547             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4548             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4549             :         "is \"geopackage\", \"table\" or \"column\"') "
    4550             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4551             :         "AND NEW.row_id_value IS NOT NULL; "
    4552             :         "END; "
    4553             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
    4554             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4555             :         "FOR EACH ROW BEGIN "
    4556             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4557             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4558             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4559             :         "WHERE NOT (NEW.timestamp GLOB "
    4560             :         "'[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-"
    4561             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4562             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4563             :         "END; "
    4564             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
    4565             :         "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
    4566             :         "FOR EACH ROW BEGIN "
    4567             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4568             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4569             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4570             :         "WHERE NOT (NEW.timestamp GLOB "
    4571             :         "'[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-"
    4572             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4573             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4574             :         "END";
    4575         380 :     if (bCreateTriggers)
    4576             :     {
    4577           0 :         osSQL += ";";
    4578           0 :         osSQL += pszMetadataReferenceTriggers;
    4579             :     }
    4580             : 
    4581         380 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4582           2 :         return false;
    4583             : 
    4584         378 :     osSQL += ";";
    4585             :     osSQL += "INSERT INTO gpkg_extensions "
    4586             :              "(table_name, column_name, extension_name, definition, scope) "
    4587             :              "VALUES "
    4588             :              "('gpkg_metadata', NULL, 'gpkg_metadata', "
    4589             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4590         378 :              "'read-write')";
    4591             : 
    4592         378 :     osSQL += ";";
    4593             :     osSQL += "INSERT INTO gpkg_extensions "
    4594             :              "(table_name, column_name, extension_name, definition, scope) "
    4595             :              "VALUES "
    4596             :              "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
    4597             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4598         378 :              "'read-write')";
    4599             : 
    4600         378 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4601         378 :     m_nHasMetadataTables = bOK;
    4602         378 :     return bOK;
    4603             : }
    4604             : 
    4605             : /************************************************************************/
    4606             : /*                           FlushMetadata()                            */
    4607             : /************************************************************************/
    4608             : 
    4609        9358 : void GDALGeoPackageDataset::FlushMetadata()
    4610             : {
    4611        9358 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4612         440 :         m_nCreateMetadataTables == FALSE)
    4613        8924 :         return;
    4614         434 :     m_bMetadataDirty = false;
    4615             : 
    4616         434 :     if (eAccess == GA_ReadOnly)
    4617             :     {
    4618           3 :         return;
    4619             :     }
    4620             : 
    4621         431 :     bool bCanWriteAreaOrPoint =
    4622         860 :         !m_bGridCellEncodingAsCO &&
    4623         429 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4624         431 :     if (!m_osRasterTable.empty())
    4625             :     {
    4626             :         const char *pszIdentifier =
    4627         147 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4628             :         const char *pszDescription =
    4629         147 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4630         176 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4631          29 :             pszIdentifier != m_osIdentifier)
    4632             :         {
    4633          14 :             m_osIdentifier = pszIdentifier;
    4634             :             char *pszSQL =
    4635          14 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4636             :                                 "WHERE lower(table_name) = lower('%q')",
    4637             :                                 pszIdentifier, m_osRasterTable.c_str());
    4638          14 :             SQLCommand(hDB, pszSQL);
    4639          14 :             sqlite3_free(pszSQL);
    4640             :         }
    4641         154 :         if (!m_bDescriptionAsCO && pszDescription != nullptr &&
    4642           7 :             pszDescription != m_osDescription)
    4643             :         {
    4644           7 :             m_osDescription = pszDescription;
    4645             :             char *pszSQL =
    4646           7 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4647             :                                 "WHERE lower(table_name) = lower('%q')",
    4648             :                                 pszDescription, m_osRasterTable.c_str());
    4649           7 :             SQLCommand(hDB, pszSQL);
    4650           7 :             sqlite3_free(pszSQL);
    4651             :         }
    4652         147 :         if (bCanWriteAreaOrPoint)
    4653             :         {
    4654             :             const char *pszAreaOrPoint =
    4655          28 :                 GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
    4656          28 :             if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
    4657             :             {
    4658          23 :                 bCanWriteAreaOrPoint = false;
    4659          23 :                 char *pszSQL = sqlite3_mprintf(
    4660             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4661             :                     "grid_cell_encoding = 'grid-value-is-area' WHERE "
    4662             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4663             :                     m_osRasterTable.c_str());
    4664          23 :                 SQLCommand(hDB, pszSQL);
    4665          23 :                 sqlite3_free(pszSQL);
    4666             :             }
    4667           5 :             else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
    4668             :             {
    4669           1 :                 bCanWriteAreaOrPoint = false;
    4670           1 :                 char *pszSQL = sqlite3_mprintf(
    4671             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4672             :                     "grid_cell_encoding = 'grid-value-is-center' WHERE "
    4673             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4674             :                     m_osRasterTable.c_str());
    4675           1 :                 SQLCommand(hDB, pszSQL);
    4676           1 :                 sqlite3_free(pszSQL);
    4677             :             }
    4678             :         }
    4679             :     }
    4680             : 
    4681         431 :     char **papszMDDup = nullptr;
    4682         208 :     for (const char *pszKeyValue :
    4683         847 :          cpl::Iterate(GDALGeoPackageDataset::GetMetadata()))
    4684             :     {
    4685         208 :         if (STARTS_WITH_CI(pszKeyValue, "IDENTIFIER="))
    4686          29 :             continue;
    4687         179 :         if (STARTS_WITH_CI(pszKeyValue, "DESCRIPTION="))
    4688           8 :             continue;
    4689         171 :         if (STARTS_WITH_CI(pszKeyValue, "ZOOM_LEVEL="))
    4690          14 :             continue;
    4691         157 :         if (STARTS_WITH_CI(pszKeyValue, "GPKG_METADATA_ITEM_"))
    4692           4 :             continue;
    4693         153 :         if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
    4694          29 :             !bCanWriteAreaOrPoint &&
    4695          26 :             STARTS_WITH_CI(pszKeyValue, GDALMD_AREA_OR_POINT))
    4696             :         {
    4697          26 :             continue;
    4698             :         }
    4699         127 :         papszMDDup = CSLInsertString(papszMDDup, -1, pszKeyValue);
    4700             :     }
    4701             : 
    4702         431 :     CPLXMLNode *psXMLNode = nullptr;
    4703             :     {
    4704         431 :         GDALMultiDomainMetadata oLocalMDMD;
    4705         431 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4706         431 :         CSLConstList papszIter = papszDomainList;
    4707         431 :         oLocalMDMD.SetMetadata(papszMDDup);
    4708         766 :         while (papszIter && *papszIter)
    4709             :         {
    4710         335 :             if (!EQUAL(*papszIter, "") &&
    4711         162 :                 !EQUAL(*papszIter, GDAL_MDD_IMAGE_STRUCTURE) &&
    4712          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4713             :             {
    4714           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4715             :                                        *papszIter);
    4716             :             }
    4717         335 :             papszIter++;
    4718             :         }
    4719         431 :         if (m_nBandCountFromMetadata > 0)
    4720             :         {
    4721          77 :             oLocalMDMD.SetMetadataItem(
    4722             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4723             :                 GDAL_MDD_IMAGE_STRUCTURE);
    4724          77 :             if (nBands == 1)
    4725             :             {
    4726          53 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4727          53 :                 if (poCT)
    4728             :                 {
    4729          16 :                     std::string osVal("{");
    4730           8 :                     const int nColorCount = poCT->GetColorEntryCount();
    4731        2056 :                     for (int i = 0; i < nColorCount; ++i)
    4732             :                     {
    4733        2048 :                         if (i > 0)
    4734        2040 :                             osVal += ',';
    4735        2048 :                         const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
    4736             :                         osVal +=
    4737        2048 :                             CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
    4738        2048 :                                        psEntry->c2, psEntry->c3, psEntry->c4);
    4739             :                     }
    4740           8 :                     osVal += '}';
    4741           8 :                     oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
    4742             :                                                GDAL_MDD_IMAGE_STRUCTURE);
    4743             :                 }
    4744             :             }
    4745          77 :             if (nBands == 1)
    4746             :             {
    4747          53 :                 const char *pszTILE_FORMAT = nullptr;
    4748          53 :                 switch (m_eTF)
    4749             :                 {
    4750           0 :                     case GPKG_TF_PNG_JPEG:
    4751           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4752           0 :                         break;
    4753          47 :                     case GPKG_TF_PNG:
    4754          47 :                         break;
    4755           0 :                     case GPKG_TF_PNG8:
    4756           0 :                         pszTILE_FORMAT = "PNG8";
    4757           0 :                         break;
    4758           3 :                     case GPKG_TF_JPEG:
    4759           3 :                         pszTILE_FORMAT = "JPEG";
    4760           3 :                         break;
    4761           3 :                     case GPKG_TF_WEBP:
    4762           3 :                         pszTILE_FORMAT = "WEBP";
    4763           3 :                         break;
    4764           0 :                     case GPKG_TF_PNG_16BIT:
    4765           0 :                         break;
    4766           0 :                     case GPKG_TF_TIFF_32BIT_FLOAT:
    4767           0 :                         break;
    4768             :                 }
    4769          53 :                 if (pszTILE_FORMAT)
    4770           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4771             :                                                GDAL_MDD_IMAGE_STRUCTURE);
    4772             :             }
    4773             :         }
    4774         578 :         if (GetRasterCount() > 0 &&
    4775         147 :             GetRasterBand(1)->GetRasterDataType() == GDT_UInt8)
    4776             :         {
    4777         117 :             int bHasNoData = FALSE;
    4778             :             const double dfNoDataValue =
    4779         117 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4780         117 :             if (bHasNoData)
    4781             :             {
    4782           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4783             :                                            CPLSPrintf("%.17g", dfNoDataValue),
    4784             :                                            GDAL_MDD_IMAGE_STRUCTURE);
    4785             :             }
    4786             :         }
    4787         683 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4788             :         {
    4789             :             auto poBand =
    4790         252 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4791         252 :             poBand->AddImplicitStatistics(false);
    4792         252 :             CSLConstList papszMD = GetRasterBand(i)->GetMetadata();
    4793         252 :             poBand->AddImplicitStatistics(true);
    4794         252 :             if (papszMD)
    4795             :             {
    4796          15 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4797             :             }
    4798             :         }
    4799         431 :         psXMLNode = oLocalMDMD.Serialize();
    4800             :     }
    4801             : 
    4802         431 :     CSLDestroy(papszMDDup);
    4803         431 :     papszMDDup = nullptr;
    4804             : 
    4805         431 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4806             : 
    4807         431 :     if (!m_osRasterTable.empty())
    4808             :     {
    4809             :         CSLConstList papszGeopackageMD =
    4810         147 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4811             : 
    4812         147 :         papszMDDup = nullptr;
    4813         147 :         for (CSLConstList papszIter = papszGeopackageMD;
    4814         156 :              papszIter && *papszIter; ++papszIter)
    4815             :         {
    4816           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4817             :         }
    4818             : 
    4819         294 :         GDALMultiDomainMetadata oLocalMDMD;
    4820         147 :         oLocalMDMD.SetMetadata(papszMDDup);
    4821         147 :         CSLDestroy(papszMDDup);
    4822         147 :         papszMDDup = nullptr;
    4823         147 :         psXMLNode = oLocalMDMD.Serialize();
    4824             : 
    4825         147 :         WriteMetadata(psXMLNode, nullptr);
    4826             :     }
    4827             : 
    4828         733 :     for (auto &poLayer : m_apoLayers)
    4829             :     {
    4830         302 :         const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
    4831         302 :         const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
    4832         302 :         if (pszIdentifier != nullptr)
    4833             :         {
    4834             :             char *pszSQL =
    4835           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4836             :                                 "WHERE lower(table_name) = lower('%q')",
    4837             :                                 pszIdentifier, poLayer->GetName());
    4838           3 :             SQLCommand(hDB, pszSQL);
    4839           3 :             sqlite3_free(pszSQL);
    4840             :         }
    4841         302 :         if (pszDescription != nullptr)
    4842             :         {
    4843             :             char *pszSQL =
    4844           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4845             :                                 "WHERE lower(table_name) = lower('%q')",
    4846             :                                 pszDescription, poLayer->GetName());
    4847           3 :             SQLCommand(hDB, pszSQL);
    4848           3 :             sqlite3_free(pszSQL);
    4849             :         }
    4850             : 
    4851         302 :         papszMDDup = nullptr;
    4852        1101 :         for (CSLConstList papszIter = poLayer->GetMetadata();
    4853        1101 :              papszIter && *papszIter; ++papszIter)
    4854             :         {
    4855         799 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4856           3 :                 continue;
    4857         796 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4858           3 :                 continue;
    4859         793 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    4860           0 :                 continue;
    4861         793 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4862             :         }
    4863             : 
    4864             :         {
    4865         302 :             GDALMultiDomainMetadata oLocalMDMD;
    4866         302 :             char **papszDomainList = poLayer->GetMetadataDomainList();
    4867         302 :             char **papszIter = papszDomainList;
    4868         302 :             oLocalMDMD.SetMetadata(papszMDDup);
    4869         667 :             while (papszIter && *papszIter)
    4870             :             {
    4871         365 :                 if (!EQUAL(*papszIter, ""))
    4872          76 :                     oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
    4873             :                                            *papszIter);
    4874         365 :                 papszIter++;
    4875             :             }
    4876         302 :             CSLDestroy(papszDomainList);
    4877         302 :             psXMLNode = oLocalMDMD.Serialize();
    4878             :         }
    4879             : 
    4880         302 :         CSLDestroy(papszMDDup);
    4881         302 :         papszMDDup = nullptr;
    4882             : 
    4883         302 :         WriteMetadata(psXMLNode, poLayer->GetName());
    4884             :     }
    4885             : }
    4886             : 
    4887             : /************************************************************************/
    4888             : /*                          GetMetadataItem()                           */
    4889             : /************************************************************************/
    4890             : 
    4891        2209 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    4892             :                                                    const char *pszDomain)
    4893             : {
    4894        2209 :     pszDomain = CheckMetadataDomain(pszDomain);
    4895        2209 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    4896             : }
    4897             : 
    4898             : /************************************************************************/
    4899             : /*                            SetMetadata()                             */
    4900             : /************************************************************************/
    4901             : 
    4902         136 : CPLErr GDALGeoPackageDataset::SetMetadata(CSLConstList papszMetadata,
    4903             :                                           const char *pszDomain)
    4904             : {
    4905         136 :     pszDomain = CheckMetadataDomain(pszDomain);
    4906         136 :     m_bMetadataDirty = true;
    4907         136 :     GetMetadata(); /* force loading from storage if needed */
    4908         136 :     return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    4909             : }
    4910             : 
    4911             : /************************************************************************/
    4912             : /*                          SetMetadataItem()                           */
    4913             : /************************************************************************/
    4914             : 
    4915          21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
    4916             :                                               const char *pszValue,
    4917             :                                               const char *pszDomain)
    4918             : {
    4919          21 :     pszDomain = CheckMetadataDomain(pszDomain);
    4920          21 :     m_bMetadataDirty = true;
    4921          21 :     GetMetadata(); /* force loading from storage if needed */
    4922          21 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    4923             : }
    4924             : 
    4925             : /************************************************************************/
    4926             : /*                               Create()                               */
    4927             : /************************************************************************/
    4928             : 
    4929        1151 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    4930             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    4931             :                                   CSLConstList papszOptions)
    4932             : {
    4933        2302 :     CPLString osCommand;
    4934             : 
    4935             :     /* First, ensure there isn't any such file yet. */
    4936             :     VSIStatBufL sStatBuf;
    4937             : 
    4938        1151 :     if (nBandsIn != 0)
    4939             :     {
    4940         232 :         if (eDT == GDT_UInt8)
    4941             :         {
    4942         162 :             if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
    4943             :                 nBandsIn != 4)
    4944             :             {
    4945           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4946             :                          "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
    4947             :                          "3 (RGB) or 4 (RGBA) band dataset supported for "
    4948             :                          "Byte datatype");
    4949           1 :                 return FALSE;
    4950             :             }
    4951             :         }
    4952          70 :         else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
    4953             :         {
    4954          43 :             if (nBandsIn != 1)
    4955             :             {
    4956           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4957             :                          "Only single band dataset supported for non Byte "
    4958             :                          "datatype");
    4959           3 :                 return FALSE;
    4960             :             }
    4961             :         }
    4962             :         else
    4963             :         {
    4964          27 :             CPLError(CE_Failure, CPLE_NotSupported,
    4965             :                      "Only Byte, Int16, UInt16 or Float32 supported");
    4966          27 :             return FALSE;
    4967             :         }
    4968             :     }
    4969             : 
    4970        1120 :     const size_t nFilenameLen = strlen(pszFilename);
    4971        1120 :     const bool bGpkgZip =
    4972        1115 :         (nFilenameLen > strlen(".gpkg.zip") &&
    4973        2235 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    4974        1115 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    4975             : 
    4976             :     const bool bUseTempFile =
    4977        1121 :         bGpkgZip || (CPLTestBool(CPLGetConfigOption(
    4978           1 :                          "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
    4979           1 :                      (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
    4980           1 :                       EQUAL(CPLGetConfigOption(
    4981             :                                 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
    4982        1120 :                             "FORCED")));
    4983             : 
    4984        1120 :     bool bFileExists = false;
    4985        1120 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
    4986             :     {
    4987          10 :         bFileExists = true;
    4988          20 :         if (nBandsIn == 0 || bUseTempFile ||
    4989          10 :             !CPLTestBool(
    4990             :                 CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
    4991             :         {
    4992           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4993             :                      "A file system object called '%s' already exists.",
    4994             :                      pszFilename);
    4995             : 
    4996           0 :             return FALSE;
    4997             :         }
    4998             :     }
    4999             : 
    5000        1120 :     if (bUseTempFile)
    5001             :     {
    5002           3 :         if (bGpkgZip)
    5003             :         {
    5004           2 :             std::string osFilenameInZip(CPLGetFilename(pszFilename));
    5005           2 :             osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
    5006             :             m_osFinalFilename =
    5007           2 :                 std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
    5008             :         }
    5009             :         else
    5010             :         {
    5011           1 :             m_osFinalFilename = pszFilename;
    5012             :         }
    5013           3 :         m_pszFilename = CPLStrdup(
    5014           6 :             CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
    5015           3 :         CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
    5016             :     }
    5017             :     else
    5018             :     {
    5019        1117 :         m_pszFilename = CPLStrdup(pszFilename);
    5020             :     }
    5021        1120 :     m_bNew = true;
    5022        1120 :     eAccess = GA_Update;
    5023        1120 :     m_bDateTimeWithTZ =
    5024        1120 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5025             :               "WITH_TZ");
    5026             : 
    5027             :     // for test/debug purposes only. true is the nominal value
    5028        1120 :     m_bPNGSupports2Bands =
    5029        1120 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5030        1120 :     m_bPNGSupportsCT =
    5031        1120 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5032             : 
    5033        1120 :     if (!OpenOrCreateDB(bFileExists
    5034             :                             ? SQLITE_OPEN_READWRITE
    5035             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5036           8 :         return FALSE;
    5037             : 
    5038             :     /* Default to synchronous=off for performance for new file */
    5039        2214 :     if (!bFileExists &&
    5040        1102 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5041             :     {
    5042         585 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5043             :     }
    5044             : 
    5045             :     /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
    5046             :     /* will be written into the main file and supported henceforth */
    5047        1112 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5048             : 
    5049        1112 :     if (bFileExists)
    5050             :     {
    5051          10 :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    5052          10 :         if (fp)
    5053             :         {
    5054             :             GByte abyHeader[100];
    5055          10 :             VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
    5056          10 :             VSIFCloseL(fp);
    5057             : 
    5058          10 :             memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
    5059          10 :             m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    5060          10 :             memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
    5061          10 :             m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    5062             : 
    5063          10 :             if (m_nApplicationId == GP10_APPLICATION_ID)
    5064             :             {
    5065           0 :                 CPLDebug("GPKG", "GeoPackage v1.0");
    5066             :             }
    5067          10 :             else if (m_nApplicationId == GP11_APPLICATION_ID)
    5068             :             {
    5069           0 :                 CPLDebug("GPKG", "GeoPackage v1.1");
    5070             :             }
    5071          10 :             else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    5072          10 :                      m_nUserVersion >= GPKG_1_2_VERSION)
    5073             :             {
    5074          10 :                 CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    5075          10 :                          (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    5076             :             }
    5077             :         }
    5078             : 
    5079          10 :         DetectSpatialRefSysColumns();
    5080             :     }
    5081             : 
    5082        1112 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5083        1112 :     if (pszVersion && !EQUAL(pszVersion, "AUTO"))
    5084             :     {
    5085          41 :         if (EQUAL(pszVersion, "1.0"))
    5086             :         {
    5087           2 :             m_nApplicationId = GP10_APPLICATION_ID;
    5088           2 :             m_nUserVersion = 0;
    5089             :         }
    5090          39 :         else if (EQUAL(pszVersion, "1.1"))
    5091             :         {
    5092           1 :             m_nApplicationId = GP11_APPLICATION_ID;
    5093           1 :             m_nUserVersion = 0;
    5094             :         }
    5095          38 :         else if (EQUAL(pszVersion, "1.2"))
    5096             :         {
    5097          15 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5098          15 :             m_nUserVersion = GPKG_1_2_VERSION;
    5099             :         }
    5100          23 :         else if (EQUAL(pszVersion, "1.3"))
    5101             :         {
    5102           3 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5103           3 :             m_nUserVersion = GPKG_1_3_VERSION;
    5104             :         }
    5105          20 :         else if (EQUAL(pszVersion, "1.4"))
    5106             :         {
    5107          20 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5108          20 :             m_nUserVersion = GPKG_1_4_VERSION;
    5109             :         }
    5110             :     }
    5111             : 
    5112        1112 :     SoftStartTransaction();
    5113             : 
    5114        2224 :     CPLString osSQL;
    5115        1112 :     if (!bFileExists)
    5116             :     {
    5117             :         /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
    5118             :          * table */
    5119             :         /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5120             :         osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
    5121             :                 "srs_name TEXT NOT NULL,"
    5122             :                 "srs_id INTEGER NOT NULL PRIMARY KEY,"
    5123             :                 "organization TEXT NOT NULL,"
    5124             :                 "organization_coordsys_id INTEGER NOT NULL,"
    5125             :                 "definition  TEXT NOT NULL,"
    5126        1102 :                 "description TEXT";
    5127        1102 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5128        1289 :                                              "NO")) ||
    5129         187 :             (nBandsIn != 0 && eDT != GDT_UInt8))
    5130             :         {
    5131          42 :             m_bHasDefinition12_063 = true;
    5132          42 :             osSQL += ", definition_12_063 TEXT NOT NULL";
    5133          42 :             if (m_nUserVersion >= GPKG_1_4_VERSION)
    5134             :             {
    5135          40 :                 osSQL += ", epoch DOUBLE";
    5136          40 :                 m_bHasEpochColumn = true;
    5137             :             }
    5138             :         }
    5139             :         osSQL += ")"
    5140             :                  ";"
    5141             :                  /* Requirement 11: The gpkg_spatial_ref_sys table in a
    5142             :                     GeoPackage SHALL */
    5143             :                  /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
    5144             :                  /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5145             : 
    5146             :                  "INSERT INTO gpkg_spatial_ref_sys ("
    5147             :                  "srs_name, srs_id, organization, organization_coordsys_id, "
    5148        1102 :                  "definition, description";
    5149        1102 :         if (m_bHasDefinition12_063)
    5150          42 :             osSQL += ", definition_12_063";
    5151             :         osSQL +=
    5152             :             ") VALUES ("
    5153             :             "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
    5154             :             "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
    5155             :             "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
    5156             :             "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
    5157             :             "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
    5158             :             "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
    5159             :             "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
    5160             :             "', 'longitude/latitude coordinates in decimal degrees on the WGS "
    5161        1102 :             "84 spheroid'";
    5162        1102 :         if (m_bHasDefinition12_063)
    5163             :             osSQL +=
    5164             :                 ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
    5165             :                 "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
    5166             :                 "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
    5167             :                 "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
    5168             :                 "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
    5169             :                 "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
    5170          42 :                 "ID[\"EPSG\", 4326]]'";
    5171             :         osSQL +=
    5172             :             ")"
    5173             :             ";"
    5174             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5175             :                SHALL */
    5176             :             /* contain a record with an srs_id of -1, an organization of “NONE”,
    5177             :              */
    5178             :             /* an organization_coordsys_id of -1, and definition “undefined” */
    5179             :             /* for undefined Cartesian coordinate reference systems */
    5180             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5181             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5182             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5183        1102 :             "definition, description";
    5184        1102 :         if (m_bHasDefinition12_063)
    5185          42 :             osSQL += ", definition_12_063";
    5186             :         osSQL += ") VALUES ("
    5187             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5188        1102 :                  "'undefined Cartesian coordinate reference system'";
    5189        1102 :         if (m_bHasDefinition12_063)
    5190          42 :             osSQL += ", 'undefined'";
    5191             :         osSQL +=
    5192             :             ")"
    5193             :             ";"
    5194             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5195             :                SHALL */
    5196             :             /* contain a record with an srs_id of 0, an organization of “NONE”,
    5197             :              */
    5198             :             /* an organization_coordsys_id of 0, and definition “undefined” */
    5199             :             /* for undefined geographic coordinate reference systems */
    5200             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5201             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5202             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5203        1102 :             "definition, description";
    5204        1102 :         if (m_bHasDefinition12_063)
    5205          42 :             osSQL += ", definition_12_063";
    5206             :         osSQL += ") VALUES ("
    5207             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5208        1102 :                  "'undefined geographic coordinate reference system'";
    5209        1102 :         if (m_bHasDefinition12_063)
    5210          42 :             osSQL += ", 'undefined'";
    5211             :         osSQL += ")"
    5212             :                  ";"
    5213             :                  /* Requirement 13: A GeoPackage file SHALL include a
    5214             :                     gpkg_contents table */
    5215             :                  /* http://opengis.github.io/geopackage/#_contents */
    5216             :                  "CREATE TABLE gpkg_contents ("
    5217             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5218             :                  "data_type TEXT NOT NULL,"
    5219             :                  "identifier TEXT UNIQUE,"
    5220             :                  "description TEXT DEFAULT '',"
    5221             :                  "last_change DATETIME NOT NULL DEFAULT "
    5222             :                  "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    5223             :                  "min_x DOUBLE, min_y DOUBLE,"
    5224             :                  "max_x DOUBLE, max_y DOUBLE,"
    5225             :                  "srs_id INTEGER,"
    5226             :                  "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
    5227             :                  "gpkg_spatial_ref_sys(srs_id)"
    5228        1102 :                  ")";
    5229             : 
    5230             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5231        1102 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5232             :         {
    5233        1096 :             m_bHasGPKGOGRContents = true;
    5234             :             osSQL += ";"
    5235             :                      "CREATE TABLE gpkg_ogr_contents("
    5236             :                      "table_name TEXT NOT NULL PRIMARY KEY,"
    5237             :                      "feature_count INTEGER DEFAULT NULL"
    5238        1096 :                      ")";
    5239             :         }
    5240             : #endif
    5241             : 
    5242             :         /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
    5243             :          * “features” */
    5244             :         /* data_type SHALL contain a gpkg_geometry_columns table or updateable
    5245             :          * view */
    5246             :         /* http://opengis.github.io/geopackage/#_geometry_columns */
    5247             :         const bool bCreateGeometryColumns =
    5248        1102 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5249        1102 :         if (bCreateGeometryColumns)
    5250             :         {
    5251        1101 :             m_bHasGPKGGeometryColumns = true;
    5252        1101 :             osSQL += ";";
    5253        1101 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5254             :         }
    5255             :     }
    5256             : 
    5257             :     const bool bCreateTriggers =
    5258        1112 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
    5259          10 :     if ((bFileExists && nBandsIn != 0 &&
    5260          10 :          SQLGetInteger(
    5261             :              hDB,
    5262             :              "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
    5263             :              "AND type in ('table', 'view')",
    5264        2224 :              nullptr) == 0) ||
    5265        1111 :         (!bFileExists &&
    5266        1102 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5267             :     {
    5268        1102 :         if (!osSQL.empty())
    5269        1101 :             osSQL += ";";
    5270             : 
    5271             :         /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
    5272             :          * Creation SQL  */
    5273             :         osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
    5274             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5275             :                  "srs_id INTEGER NOT NULL,"
    5276             :                  "min_x DOUBLE NOT NULL,"
    5277             :                  "min_y DOUBLE NOT NULL,"
    5278             :                  "max_x DOUBLE NOT NULL,"
    5279             :                  "max_y DOUBLE NOT NULL,"
    5280             :                  "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
    5281             :                  "REFERENCES gpkg_contents(table_name),"
    5282             :                  "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
    5283             :                  "gpkg_spatial_ref_sys (srs_id)"
    5284             :                  ")"
    5285             :                  ";"
    5286             : 
    5287             :                  /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
    5288             :                     Creation SQL */
    5289             :                  "CREATE TABLE gpkg_tile_matrix ("
    5290             :                  "table_name TEXT NOT NULL,"
    5291             :                  "zoom_level INTEGER NOT NULL,"
    5292             :                  "matrix_width INTEGER NOT NULL,"
    5293             :                  "matrix_height INTEGER NOT NULL,"
    5294             :                  "tile_width INTEGER NOT NULL,"
    5295             :                  "tile_height INTEGER NOT NULL,"
    5296             :                  "pixel_x_size DOUBLE NOT NULL,"
    5297             :                  "pixel_y_size DOUBLE NOT NULL,"
    5298             :                  "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
    5299             :                  "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
    5300             :                  "REFERENCES gpkg_contents(table_name)"
    5301        1102 :                  ")";
    5302             : 
    5303        1102 :         if (bCreateTriggers)
    5304             :         {
    5305             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5306             :              * Definition SQL */
    5307        1102 :             const char *pszTileMatrixTrigger =
    5308             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
    5309             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5310             :                 "FOR EACH ROW BEGIN "
    5311             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5312             :                 "violates constraint: zoom_level cannot be less than 0') "
    5313             :                 "WHERE (NEW.zoom_level < 0); "
    5314             :                 "END; "
    5315             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
    5316             :                 "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
    5317             :                 "FOR EACH ROW BEGIN "
    5318             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5319             :                 "violates constraint: zoom_level cannot be less than 0') "
    5320             :                 "WHERE (NEW.zoom_level < 0); "
    5321             :                 "END; "
    5322             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
    5323             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5324             :                 "FOR EACH ROW BEGIN "
    5325             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5326             :                 "violates constraint: matrix_width cannot be less than 1') "
    5327             :                 "WHERE (NEW.matrix_width < 1); "
    5328             :                 "END; "
    5329             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
    5330             :                 "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
    5331             :                 "FOR EACH ROW BEGIN "
    5332             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5333             :                 "violates constraint: matrix_width cannot be less than 1') "
    5334             :                 "WHERE (NEW.matrix_width < 1); "
    5335             :                 "END; "
    5336             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
    5337             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5338             :                 "FOR EACH ROW BEGIN "
    5339             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5340             :                 "violates constraint: matrix_height cannot be less than 1') "
    5341             :                 "WHERE (NEW.matrix_height < 1); "
    5342             :                 "END; "
    5343             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
    5344             :                 "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
    5345             :                 "FOR EACH ROW BEGIN "
    5346             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5347             :                 "violates constraint: matrix_height cannot be less than 1') "
    5348             :                 "WHERE (NEW.matrix_height < 1); "
    5349             :                 "END; "
    5350             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
    5351             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5352             :                 "FOR EACH ROW BEGIN "
    5353             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5354             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5355             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5356             :                 "END; "
    5357             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
    5358             :                 "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
    5359             :                 "FOR EACH ROW BEGIN "
    5360             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5361             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5362             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5363             :                 "END; "
    5364             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
    5365             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5366             :                 "FOR EACH ROW BEGIN "
    5367             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5368             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5369             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5370             :                 "END; "
    5371             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
    5372             :                 "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
    5373             :                 "FOR EACH ROW BEGIN "
    5374             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5375             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5376             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5377             :                 "END;";
    5378        1102 :             osSQL += ";";
    5379        1102 :             osSQL += pszTileMatrixTrigger;
    5380             :         }
    5381             :     }
    5382             : 
    5383        1112 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5384           1 :         return FALSE;
    5385             : 
    5386        1111 :     if (!bFileExists)
    5387             :     {
    5388             :         const char *pszMetadataTables =
    5389        1101 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5390        1101 :         if (pszMetadataTables)
    5391          10 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5392             : 
    5393        1101 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5394           0 :             return FALSE;
    5395             : 
    5396        1101 :         if (m_bHasDefinition12_063)
    5397             :         {
    5398          84 :             if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
    5399             :                 OGRERR_NONE !=
    5400          42 :                     SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5401             :                                     "(table_name, column_name, extension_name, "
    5402             :                                     "definition, scope) "
    5403             :                                     "VALUES "
    5404             :                                     "('gpkg_spatial_ref_sys', "
    5405             :                                     "'definition_12_063', 'gpkg_crs_wkt', "
    5406             :                                     "'http://www.geopackage.org/spec120/"
    5407             :                                     "#extension_crs_wkt', 'read-write')"))
    5408             :             {
    5409           0 :                 return FALSE;
    5410             :             }
    5411          42 :             if (m_bHasEpochColumn)
    5412             :             {
    5413          40 :                 if (OGRERR_NONE !=
    5414          40 :                         SQLCommand(
    5415             :                             hDB, "UPDATE gpkg_extensions SET extension_name = "
    5416             :                                  "'gpkg_crs_wkt_1_1' "
    5417          80 :                                  "WHERE extension_name = 'gpkg_crs_wkt'") ||
    5418             :                     OGRERR_NONE !=
    5419          40 :                         SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5420             :                                         "(table_name, column_name, "
    5421             :                                         "extension_name, definition, scope) "
    5422             :                                         "VALUES "
    5423             :                                         "('gpkg_spatial_ref_sys', 'epoch', "
    5424             :                                         "'gpkg_crs_wkt_1_1', "
    5425             :                                         "'http://www.geopackage.org/spec/"
    5426             :                                         "#extension_crs_wkt', "
    5427             :                                         "'read-write')"))
    5428             :                 {
    5429           0 :                     return FALSE;
    5430             :                 }
    5431             :             }
    5432             :         }
    5433             :     }
    5434             : 
    5435        1111 :     if (nBandsIn != 0)
    5436             :     {
    5437         196 :         const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
    5438             :         m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
    5439         196 :                                                osTableName.c_str());
    5440         196 :         if (m_osRasterTable.empty())
    5441             :         {
    5442           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5443             :                      "RASTER_TABLE must be set to a non empty value");
    5444           0 :             return FALSE;
    5445             :         }
    5446         196 :         m_bIdentifierAsCO =
    5447         196 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5448             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5449         196 :                                               m_osRasterTable);
    5450         196 :         m_bDescriptionAsCO =
    5451         196 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5452             :         m_osDescription =
    5453         196 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5454         196 :         SetDataType(eDT);
    5455         196 :         if (eDT == GDT_Int16)
    5456          16 :             SetGlobalOffsetScale(-32768.0, 1.0);
    5457             : 
    5458             :         /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
    5459             :          * table Create Table SQL (Informative) */
    5460             :         char *pszSQL =
    5461         196 :             sqlite3_mprintf("CREATE TABLE \"%w\" ("
    5462             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    5463             :                             "zoom_level INTEGER NOT NULL,"
    5464             :                             "tile_column INTEGER NOT NULL,"
    5465             :                             "tile_row INTEGER NOT NULL,"
    5466             :                             "tile_data BLOB NOT NULL,"
    5467             :                             "UNIQUE (zoom_level, tile_column, tile_row)"
    5468             :                             ")",
    5469             :                             m_osRasterTable.c_str());
    5470         196 :         osSQL = pszSQL;
    5471         196 :         sqlite3_free(pszSQL);
    5472             : 
    5473         196 :         if (bCreateTriggers)
    5474             :         {
    5475         196 :             osSQL += ";";
    5476         196 :             osSQL += CreateRasterTriggersSQL(m_osRasterTable);
    5477             :         }
    5478             : 
    5479         196 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5480         196 :         if (OGRERR_NONE != eErr)
    5481           0 :             return FALSE;
    5482             : 
    5483         196 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5484         196 :         if (eDT == GDT_Int16 || eDT == GDT_UInt16)
    5485             :         {
    5486          27 :             m_eTF = GPKG_TF_PNG_16BIT;
    5487          27 :             if (pszTF)
    5488             :             {
    5489           1 :                 if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
    5490             :                 {
    5491           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5492             :                              "Only AUTO or PNG supported "
    5493             :                              "as tile format for Int16 / UInt16");
    5494             :                 }
    5495             :             }
    5496             :         }
    5497         169 :         else if (eDT == GDT_Float32)
    5498             :         {
    5499          13 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    5500          13 :             if (pszTF)
    5501             :             {
    5502           5 :                 if (EQUAL(pszTF, "PNG"))
    5503           5 :                     m_eTF = GPKG_TF_PNG_16BIT;
    5504           0 :                 else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
    5505             :                 {
    5506           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5507             :                              "Only AUTO, PNG or TIFF supported "
    5508             :                              "as tile format for Float32");
    5509             :                 }
    5510             :             }
    5511             :         }
    5512             :         else
    5513             :         {
    5514         156 :             if (pszTF)
    5515             :             {
    5516          71 :                 m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    5517          71 :                 if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
    5518           7 :                     m_bMetadataDirty = true;
    5519             :             }
    5520          85 :             else if (nBandsIn == 1)
    5521          74 :                 m_eTF = GPKG_TF_PNG;
    5522             :         }
    5523             : 
    5524         196 :         if (eDT != GDT_UInt8)
    5525             :         {
    5526          40 :             if (!CreateTileGriddedTable(papszOptions))
    5527           0 :                 return FALSE;
    5528             :         }
    5529             : 
    5530         196 :         nRasterXSize = nXSize;
    5531         196 :         nRasterYSize = nYSize;
    5532             : 
    5533             :         const char *pszTileSize =
    5534         196 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5535             :         const char *pszTileWidth =
    5536         196 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5537             :         const char *pszTileHeight =
    5538         196 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5539         196 :         int nTileWidth = atoi(pszTileWidth);
    5540         196 :         int nTileHeight = atoi(pszTileHeight);
    5541         196 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5542         392 :              nTileHeight > 4096) &&
    5543           1 :             !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
    5544             :         {
    5545           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5546             :                      "Invalid block dimensions: %dx%d", nTileWidth,
    5547             :                      nTileHeight);
    5548           0 :             return FALSE;
    5549             :         }
    5550             : 
    5551         525 :         for (int i = 1; i <= nBandsIn; i++)
    5552             :         {
    5553         329 :             SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
    5554             :                            this, nTileWidth, nTileHeight));
    5555             :         }
    5556             : 
    5557         196 :         GDALPamDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
    5558             :                                         GDAL_MDD_IMAGE_STRUCTURE);
    5559         196 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5560         196 :         if (!m_osDescription.empty())
    5561           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5562             : 
    5563         196 :         ParseCompressionOptions(papszOptions);
    5564             : 
    5565         196 :         if (m_eTF == GPKG_TF_WEBP)
    5566             :         {
    5567          10 :             if (!RegisterWebPExtension())
    5568           0 :                 return FALSE;
    5569             :         }
    5570             : 
    5571             :         m_osTilingScheme =
    5572         196 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5573         196 :         if (!EQUAL(m_osTilingScheme, "CUSTOM"))
    5574             :         {
    5575          22 :             const auto poTS = GetTilingScheme(m_osTilingScheme);
    5576          22 :             if (!poTS)
    5577           0 :                 return FALSE;
    5578             : 
    5579          43 :             if (nTileWidth != poTS->nTileWidth ||
    5580          21 :                 nTileHeight != poTS->nTileHeight)
    5581             :             {
    5582           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5583             :                          "Tile dimension should be %dx%d for %s tiling scheme",
    5584           1 :                          poTS->nTileWidth, poTS->nTileHeight,
    5585             :                          m_osTilingScheme.c_str());
    5586           1 :                 return FALSE;
    5587             :             }
    5588             : 
    5589             :             const char *pszZoomLevel =
    5590          21 :                 CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    5591          21 :             if (pszZoomLevel)
    5592             :             {
    5593           1 :                 m_nZoomLevel = atoi(pszZoomLevel);
    5594           1 :                 int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    5595           1 :                 while ((1 << nMaxZoomLevelForThisTM) >
    5596           2 :                            INT_MAX / poTS->nTileXCountZoomLevel0 ||
    5597           1 :                        (1 << nMaxZoomLevelForThisTM) >
    5598           1 :                            INT_MAX / poTS->nTileYCountZoomLevel0)
    5599             :                 {
    5600           0 :                     --nMaxZoomLevelForThisTM;
    5601             :                 }
    5602             : 
    5603           1 :                 if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
    5604             :                 {
    5605           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5606             :                              "ZOOM_LEVEL = %s is invalid. It should be in "
    5607             :                              "[0,%d] range",
    5608             :                              pszZoomLevel, nMaxZoomLevelForThisTM);
    5609           0 :                     return FALSE;
    5610             :                 }
    5611             :             }
    5612             : 
    5613             :             // Implicitly sets SRS.
    5614          21 :             OGRSpatialReference oSRS;
    5615          21 :             if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
    5616           0 :                 return FALSE;
    5617          21 :             char *pszWKT = nullptr;
    5618          21 :             oSRS.exportToWkt(&pszWKT);
    5619          21 :             SetProjection(pszWKT);
    5620          21 :             CPLFree(pszWKT);
    5621             :         }
    5622             :         else
    5623             :         {
    5624         174 :             if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5625             :             {
    5626           0 :                 CPLError(
    5627             :                     CE_Failure, CPLE_NotSupported,
    5628             :                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5629           0 :                 return false;
    5630             :             }
    5631             :         }
    5632             :     }
    5633             : 
    5634        1110 :     if (bFileExists && nBandsIn > 0 && eDT == GDT_UInt8)
    5635             :     {
    5636             :         // If there was an ogr_empty_table table, we can remove it
    5637           9 :         RemoveOGREmptyTable();
    5638             :     }
    5639             : 
    5640        1110 :     SoftCommitTransaction();
    5641             : 
    5642             :     /* Requirement 2 */
    5643             :     /* We have to do this after there's some content so the database file */
    5644             :     /* is not zero length */
    5645        1110 :     SetApplicationAndUserVersionId();
    5646             : 
    5647             :     /* Default to synchronous=off for performance for new file */
    5648        2210 :     if (!bFileExists &&
    5649        1100 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5650             :     {
    5651         585 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5652             :     }
    5653             : 
    5654             :     // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
    5655             :     // that take geometries will accept and return GPKG encoded geometries without
    5656             :     // explicit conversion.
    5657             :     // Note: we need to do that after DB creation, since EnableGpkgMode()
    5658             :     // checks for the presence of GPKG system tables.
    5659        1110 :     sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
    5660             : 
    5661        1110 :     return TRUE;
    5662             : }
    5663             : 
    5664             : /************************************************************************/
    5665             : /*                        RemoveOGREmptyTable()                         */
    5666             : /************************************************************************/
    5667             : 
    5668         925 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5669             : {
    5670             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5671         925 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5672             :                  nullptr);
    5673         925 :     sqlite3_exec(
    5674             :         hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
    5675             :         nullptr, nullptr, nullptr);
    5676             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5677         925 :     if (m_bHasGPKGOGRContents)
    5678             :     {
    5679         910 :         sqlite3_exec(hDB,
    5680             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5681             :                      "table_name = 'ogr_empty_table'",
    5682             :                      nullptr, nullptr, nullptr);
    5683             :     }
    5684             : #endif
    5685         925 :     sqlite3_exec(hDB,
    5686             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5687             :                  "table_name = 'ogr_empty_table'",
    5688             :                  nullptr, nullptr, nullptr);
    5689         925 : }
    5690             : 
    5691             : /************************************************************************/
    5692             : /*                       CreateTileGriddedTable()                       */
    5693             : /************************************************************************/
    5694             : 
    5695          40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(CSLConstList papszOptions)
    5696             : {
    5697          80 :     CPLString osSQL;
    5698          40 :     if (!HasGriddedCoverageAncillaryTable())
    5699             :     {
    5700             :         // It doesn't exist. So create gpkg_extensions table if necessary, and
    5701             :         // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
    5702             :         // and register them as extensions.
    5703          40 :         if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    5704           0 :             return false;
    5705             : 
    5706             :         // Req 1 /table-defs/coverage-ancillary
    5707             :         osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
    5708             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5709             :                 "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
    5710             :                 "datatype TEXT NOT NULL DEFAULT 'integer',"
    5711             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5712             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5713             :                 "precision REAL DEFAULT 1.0,"
    5714             :                 "data_null REAL,"
    5715             :                 "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
    5716             :                 "uom TEXT,"
    5717             :                 "field_name TEXT DEFAULT 'Height',"
    5718             :                 "quantity_definition TEXT DEFAULT 'Height',"
    5719             :                 "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
    5720             :                 "REFERENCES gpkg_tile_matrix_set ( table_name ) "
    5721             :                 "CHECK (datatype in ('integer','float')))"
    5722             :                 ";"
    5723             :                 // Requirement 2 /table-defs/tile-ancillary
    5724             :                 "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
    5725             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5726             :                 "tpudt_name TEXT NOT NULL,"
    5727             :                 "tpudt_id INTEGER NOT NULL,"
    5728             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5729             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5730             :                 "min REAL DEFAULT NULL,"
    5731             :                 "max REAL DEFAULT NULL,"
    5732             :                 "mean REAL DEFAULT NULL,"
    5733             :                 "std_dev REAL DEFAULT NULL,"
    5734             :                 "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
    5735             :                 "REFERENCES gpkg_contents(table_name),"
    5736             :                 "UNIQUE (tpudt_name, tpudt_id))"
    5737             :                 ";"
    5738             :                 // Requirement 6 /gpkg-extensions
    5739             :                 "INSERT INTO gpkg_extensions "
    5740             :                 "(table_name, column_name, extension_name, definition, scope) "
    5741             :                 "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
    5742             :                 "'gpkg_2d_gridded_coverage', "
    5743             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5744             :                 "'read-write')"
    5745             :                 ";"
    5746             :                 // Requirement 6 /gpkg-extensions
    5747             :                 "INSERT INTO gpkg_extensions "
    5748             :                 "(table_name, column_name, extension_name, definition, scope) "
    5749             :                 "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
    5750             :                 "'gpkg_2d_gridded_coverage', "
    5751             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5752             :                 "'read-write')"
    5753          40 :                 ";";
    5754             :     }
    5755             : 
    5756             :     // Requirement 6 /gpkg-extensions
    5757          40 :     char *pszSQL = sqlite3_mprintf(
    5758             :         "INSERT INTO gpkg_extensions "
    5759             :         "(table_name, column_name, extension_name, definition, scope) "
    5760             :         "VALUES ('%q', 'tile_data', "
    5761             :         "'gpkg_2d_gridded_coverage', "
    5762             :         "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5763             :         "'read-write')",
    5764             :         m_osRasterTable.c_str());
    5765          40 :     osSQL += pszSQL;
    5766          40 :     osSQL += ";";
    5767          40 :     sqlite3_free(pszSQL);
    5768             : 
    5769             :     // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
    5770             :     // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
    5771             :     // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
    5772          40 :     m_dfPrecision =
    5773          40 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
    5774             :     CPLString osGridCellEncoding(CSLFetchNameValueDef(
    5775          80 :         papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
    5776          40 :     m_bGridCellEncodingAsCO =
    5777          40 :         CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
    5778          80 :     CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
    5779             :     CPLString osFieldName(
    5780          80 :         CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
    5781             :     CPLString osQuantityDefinition(
    5782          80 :         CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
    5783             : 
    5784         121 :     pszSQL = sqlite3_mprintf(
    5785             :         "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
    5786             :         "(tile_matrix_set_name, datatype, scale, offset, precision, "
    5787             :         "grid_cell_encoding, uom, field_name, quantity_definition) "
    5788             :         "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
    5789             :         m_osRasterTable.c_str(),
    5790          40 :         (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
    5791             :         m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
    5792          41 :         osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
    5793             :         osQuantityDefinition.c_str());
    5794          40 :     m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
    5795          40 :     sqlite3_free(pszSQL);
    5796             : 
    5797             :     // Requirement 3 /gpkg-spatial-ref-sys-row
    5798             :     auto oResultTable = SQLQuery(
    5799          80 :         hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
    5800          40 :     bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
    5801          40 :     if (!bHasEPSG4979)
    5802             :     {
    5803          41 :         if (!m_bHasDefinition12_063 &&
    5804           1 :             !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
    5805             :         {
    5806           0 :             return false;
    5807             :         }
    5808             : 
    5809             :         // This is WKT 2...
    5810          40 :         const char *pszWKT =
    5811             :             "GEODCRS[\"WGS 84\","
    5812             :             "DATUM[\"World Geodetic System 1984\","
    5813             :             "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
    5814             :             "LENGTHUNIT[\"metre\",1.0]]],"
    5815             :             "CS[ellipsoidal,3],"
    5816             :             "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
    5817             :             "0.0174532925199433]],"
    5818             :             "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
    5819             :             "0.0174532925199433]],"
    5820             :             "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
    5821             :             "LENGTHUNIT[\"metre\",1.0]],"
    5822             :             "ID[\"EPSG\",4979]]";
    5823             : 
    5824          40 :         pszSQL = sqlite3_mprintf(
    5825             :             "INSERT INTO gpkg_spatial_ref_sys "
    5826             :             "(srs_name,srs_id,organization,organization_coordsys_id,"
    5827             :             "definition,definition_12_063) VALUES "
    5828             :             "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
    5829             :             pszWKT);
    5830          40 :         osSQL += ";";
    5831          40 :         osSQL += pszSQL;
    5832          40 :         sqlite3_free(pszSQL);
    5833             :     }
    5834             : 
    5835          40 :     return SQLCommand(hDB, osSQL) == OGRERR_NONE;
    5836             : }
    5837             : 
    5838             : /************************************************************************/
    5839             : /*                  HasGriddedCoverageAncillaryTable()                  */
    5840             : /************************************************************************/
    5841             : 
    5842          44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
    5843             : {
    5844             :     auto oResultTable = SQLQuery(
    5845             :         hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
    5846          44 :              "name = 'gpkg_2d_gridded_coverage_ancillary'");
    5847          44 :     bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
    5848          88 :     return bHasTable;
    5849             : }
    5850             : 
    5851             : /************************************************************************/
    5852             : /*                        GetUnderlyingDataset()                        */
    5853             : /************************************************************************/
    5854             : 
    5855           3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
    5856             : {
    5857           3 :     if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
    5858             :     {
    5859           0 :         auto poTmpDS = poVRTDS->GetSingleSimpleSource();
    5860           0 :         if (poTmpDS)
    5861           0 :             return poTmpDS;
    5862             :     }
    5863             : 
    5864           3 :     return poSrcDS;
    5865             : }
    5866             : 
    5867             : /************************************************************************/
    5868             : /*                             CreateCopy()                             */
    5869             : /************************************************************************/
    5870             : 
    5871             : typedef struct
    5872             : {
    5873             :     const char *pszName;
    5874             :     GDALResampleAlg eResampleAlg;
    5875             : } WarpResamplingAlg;
    5876             : 
    5877             : static const WarpResamplingAlg asResamplingAlg[] = {
    5878             :     {"NEAREST", GRA_NearestNeighbour},
    5879             :     {"BILINEAR", GRA_Bilinear},
    5880             :     {"CUBIC", GRA_Cubic},
    5881             :     {"CUBICSPLINE", GRA_CubicSpline},
    5882             :     {"LANCZOS", GRA_Lanczos},
    5883             :     {"MODE", GRA_Mode},
    5884             :     {"AVERAGE", GRA_Average},
    5885             :     {"RMS", GRA_RMS},
    5886             : };
    5887             : 
    5888         165 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    5889             :                                                GDALDataset *poSrcDS,
    5890             :                                                int bStrict,
    5891             :                                                CSLConstList papszOptions,
    5892             :                                                GDALProgressFunc pfnProgress,
    5893             :                                                void *pProgressData)
    5894             : {
    5895         165 :     const int nBands = poSrcDS->GetRasterCount();
    5896         165 :     if (nBands == 0)
    5897             :     {
    5898           2 :         GDALDataset *poDS = nullptr;
    5899             :         GDALDriver *poThisDriver =
    5900           2 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    5901           2 :         if (poThisDriver != nullptr)
    5902             :         {
    5903           2 :             poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
    5904             :                                                    bStrict, papszOptions,
    5905             :                                                    pfnProgress, pProgressData);
    5906             :         }
    5907           2 :         return poDS;
    5908             :     }
    5909             : 
    5910             :     const char *pszTilingScheme =
    5911         163 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5912             : 
    5913         326 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    5914         163 :     if (CPLTestBool(
    5915         169 :             CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
    5916           6 :         CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
    5917             :     {
    5918             :         const std::string osBasename(CPLGetBasenameSafe(
    5919           6 :             GetUnderlyingDataset(poSrcDS)->GetDescription()));
    5920           3 :         apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
    5921             :     }
    5922             : 
    5923         163 :     if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
    5924             :     {
    5925           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    5926             :                  "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
    5927             :                  "4 (RGBA) band dataset supported");
    5928           1 :         return nullptr;
    5929             :     }
    5930             : 
    5931         162 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    5932         324 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    5933         162 :         !EQUAL(pszUnitType, ""))
    5934             :     {
    5935           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    5936             :     }
    5937             : 
    5938         162 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    5939             :     {
    5940         138 :         if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5941             :         {
    5942           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    5943             :                      "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5944           0 :             return nullptr;
    5945             :         }
    5946             : 
    5947         138 :         GDALGeoPackageDataset *poDS = nullptr;
    5948             :         GDALDriver *poThisDriver =
    5949         138 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    5950         138 :         if (poThisDriver != nullptr)
    5951             :         {
    5952         138 :             apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
    5953         138 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    5954             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    5955             :                                                 apszUpdatedOptions, pfnProgress,
    5956         138 :                                                 pProgressData));
    5957             : 
    5958         256 :             if (poDS != nullptr &&
    5959         138 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_UInt8 &&
    5960             :                 nBands <= 3)
    5961             :             {
    5962          78 :                 poDS->m_nBandCountFromMetadata = nBands;
    5963          78 :                 poDS->m_bMetadataDirty = true;
    5964             :             }
    5965             :         }
    5966         138 :         if (poDS)
    5967         118 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    5968         138 :         return poDS;
    5969             :     }
    5970             : 
    5971          48 :     const auto poTS = GetTilingScheme(pszTilingScheme);
    5972          24 :     if (!poTS)
    5973             :     {
    5974           2 :         return nullptr;
    5975             :     }
    5976          22 :     const int nEPSGCode = poTS->nEPSGCode;
    5977             : 
    5978          44 :     OGRSpatialReference oSRS;
    5979          22 :     if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
    5980             :     {
    5981           0 :         return nullptr;
    5982             :     }
    5983          22 :     char *pszWKT = nullptr;
    5984          22 :     oSRS.exportToWkt(&pszWKT);
    5985          22 :     char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
    5986             : 
    5987          22 :     void *hTransformArg = nullptr;
    5988             : 
    5989             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    5990             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    5991             :     // EPSG:3857.
    5992          22 :     GDALGeoTransform srcGT;
    5993          22 :     std::unique_ptr<GDALDataset> poTmpDS;
    5994          22 :     bool bEPSG3857Adjust = false;
    5995           8 :     if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
    5996          30 :         srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
    5997             :     {
    5998           8 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    5999           8 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    6000             :         {
    6001           2 :             double maxLat = srcGT[3];
    6002           2 :             double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
    6003             :             // Corresponds to the latitude of below MAX_GM
    6004           2 :             constexpr double MAX_LAT = 85.0511287798066;
    6005           2 :             bool bModified = false;
    6006           2 :             if (maxLat > MAX_LAT)
    6007             :             {
    6008           2 :                 maxLat = MAX_LAT;
    6009           2 :                 bModified = true;
    6010             :             }
    6011           2 :             if (minLat < -MAX_LAT)
    6012             :             {
    6013           2 :                 minLat = -MAX_LAT;
    6014           2 :                 bModified = true;
    6015             :             }
    6016           2 :             if (bModified)
    6017             :             {
    6018           4 :                 CPLStringList aosOptions;
    6019           2 :                 aosOptions.AddString("-of");
    6020           2 :                 aosOptions.AddString("VRT");
    6021           2 :                 aosOptions.AddString("-projwin");
    6022           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
    6023           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    6024             :                 aosOptions.AddString(CPLSPrintf(
    6025           2 :                     "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[1]));
    6026           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    6027             :                 auto psOptions =
    6028           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    6029           2 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    6030             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    6031           2 :                 GDALTranslateOptionsFree(psOptions);
    6032           2 :                 if (poTmpDS)
    6033             :                 {
    6034           2 :                     bEPSG3857Adjust = true;
    6035           2 :                     hTransformArg = GDALCreateGenImgProjTransformer2(
    6036           2 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    6037             :                         papszTO);
    6038             :                 }
    6039             :             }
    6040             :         }
    6041             :     }
    6042          22 :     if (hTransformArg == nullptr)
    6043             :     {
    6044             :         hTransformArg =
    6045          20 :             GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
    6046             :     }
    6047             : 
    6048          22 :     if (hTransformArg == nullptr)
    6049             :     {
    6050           1 :         CPLFree(pszWKT);
    6051           1 :         CSLDestroy(papszTO);
    6052           1 :         return nullptr;
    6053             :     }
    6054             : 
    6055          21 :     GDALTransformerInfo *psInfo =
    6056             :         static_cast<GDALTransformerInfo *>(hTransformArg);
    6057          21 :     GDALGeoTransform gt;
    6058             :     double adfExtent[4];
    6059             :     int nXSize, nYSize;
    6060             : 
    6061          21 :     if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
    6062             :                                  gt.data(), &nXSize, &nYSize, adfExtent,
    6063          21 :                                  0) != CE_None)
    6064             :     {
    6065           0 :         CPLFree(pszWKT);
    6066           0 :         CSLDestroy(papszTO);
    6067           0 :         GDALDestroyGenImgProjTransformer(hTransformArg);
    6068           0 :         return nullptr;
    6069             :     }
    6070             : 
    6071          21 :     GDALDestroyGenImgProjTransformer(hTransformArg);
    6072          21 :     hTransformArg = nullptr;
    6073          21 :     poTmpDS.reset();
    6074             : 
    6075          21 :     if (bEPSG3857Adjust)
    6076             :     {
    6077           2 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    6078           2 :         constexpr double MAX_GM =
    6079             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    6080           2 :         double maxNorthing = gt[3];
    6081           2 :         double minNorthing = gt[3] + gt[5] * nYSize;
    6082           2 :         bool bChanged = false;
    6083           2 :         if (maxNorthing > MAX_GM)
    6084             :         {
    6085           2 :             bChanged = true;
    6086           2 :             maxNorthing = MAX_GM;
    6087             :         }
    6088           2 :         if (minNorthing < -MAX_GM)
    6089             :         {
    6090           2 :             bChanged = true;
    6091           2 :             minNorthing = -MAX_GM;
    6092             :         }
    6093           2 :         if (bChanged)
    6094             :         {
    6095           2 :             gt[3] = maxNorthing;
    6096           2 :             nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
    6097           2 :             adfExtent[1] = maxNorthing + nYSize * gt[5];
    6098           2 :             adfExtent[3] = maxNorthing;
    6099             :         }
    6100             :     }
    6101             : 
    6102          21 :     double dfComputedRes = gt[1];
    6103          21 :     double dfPrevRes = 0.0;
    6104          21 :     double dfRes = 0.0;
    6105          21 :     int nZoomLevel = 0;  // Used after for.
    6106          21 :     const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    6107          21 :     if (pszZoomLevel)
    6108             :     {
    6109           2 :         nZoomLevel = atoi(pszZoomLevel);
    6110             : 
    6111           2 :         int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    6112           2 :         while ((1 << nMaxZoomLevelForThisTM) >
    6113           4 :                    INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6114           2 :                (1 << nMaxZoomLevelForThisTM) >
    6115           2 :                    INT_MAX / poTS->nTileYCountZoomLevel0)
    6116             :         {
    6117           0 :             --nMaxZoomLevelForThisTM;
    6118             :         }
    6119             : 
    6120           2 :         if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
    6121             :         {
    6122           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6123             :                      "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
    6124             :                      pszZoomLevel, nMaxZoomLevelForThisTM);
    6125           1 :             CPLFree(pszWKT);
    6126           1 :             CSLDestroy(papszTO);
    6127           1 :             return nullptr;
    6128             :         }
    6129             :     }
    6130             :     else
    6131             :     {
    6132         171 :         for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
    6133             :         {
    6134         171 :             dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6135         171 :             if (dfComputedRes > dfRes ||
    6136         152 :                 fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
    6137             :                 break;
    6138         152 :             dfPrevRes = dfRes;
    6139             :         }
    6140          38 :         if (nZoomLevel == MAX_ZOOM_LEVEL ||
    6141          38 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6142          19 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
    6143             :         {
    6144           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6145             :                      "Could not find an appropriate zoom level");
    6146           0 :             CPLFree(pszWKT);
    6147           0 :             CSLDestroy(papszTO);
    6148           0 :             return nullptr;
    6149             :         }
    6150             : 
    6151          19 :         if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
    6152             :         {
    6153          17 :             const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
    6154             :                 papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
    6155          17 :             if (EQUAL(pszZoomLevelStrategy, "LOWER"))
    6156             :             {
    6157           1 :                 nZoomLevel--;
    6158             :             }
    6159          16 :             else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
    6160             :             {
    6161             :                 /* do nothing */
    6162             :             }
    6163             :             else
    6164             :             {
    6165          15 :                 if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    6166          13 :                     nZoomLevel--;
    6167             :             }
    6168             :         }
    6169             :     }
    6170             : 
    6171          20 :     dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6172             : 
    6173          20 :     double dfMinX = adfExtent[0];
    6174          20 :     double dfMinY = adfExtent[1];
    6175          20 :     double dfMaxX = adfExtent[2];
    6176          20 :     double dfMaxY = adfExtent[3];
    6177             : 
    6178          20 :     nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
    6179          20 :     nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
    6180          20 :     gt[1] = dfRes;
    6181          20 :     gt[5] = -dfRes;
    6182             : 
    6183          20 :     const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    6184          20 :     int nTargetBands = nBands;
    6185             :     /* For grey level or RGB, if there's reprojection involved, add an alpha */
    6186             :     /* channel */
    6187          37 :     if (eDT == GDT_UInt8 &&
    6188          13 :         ((nBands == 1 &&
    6189          17 :           poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
    6190             :          nBands == 3))
    6191             :     {
    6192          30 :         OGRSpatialReference oSrcSRS;
    6193          15 :         oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
    6194          15 :         oSrcSRS.AutoIdentifyEPSG();
    6195          30 :         if (oSrcSRS.GetAuthorityCode() == nullptr ||
    6196          15 :             atoi(oSrcSRS.GetAuthorityCode()) != nEPSGCode)
    6197             :         {
    6198          13 :             nTargetBands++;
    6199             :         }
    6200             :     }
    6201             : 
    6202          20 :     GDALResampleAlg eResampleAlg = GRA_Bilinear;
    6203          20 :     const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
    6204          20 :     if (pszResampling)
    6205             :     {
    6206           6 :         for (size_t iAlg = 0;
    6207           6 :              iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
    6208             :              iAlg++)
    6209             :         {
    6210           6 :             if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
    6211             :             {
    6212           3 :                 eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
    6213           3 :                 break;
    6214             :             }
    6215             :         }
    6216             :     }
    6217             : 
    6218          16 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    6219          36 :         eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
    6220             :     {
    6221           0 :         CPLError(
    6222             :             CE_Warning, CPLE_AppDefined,
    6223             :             "Input dataset has a color table, which will likely lead to "
    6224             :             "bad results when using a resampling method other than "
    6225             :             "nearest neighbour or mode. Converting the dataset to 24/32 bit "
    6226             :             "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
    6227             :     }
    6228             : 
    6229          40 :     auto poDS = std::make_unique<GDALGeoPackageDataset>();
    6230          40 :     if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
    6231          20 :                        apszUpdatedOptions)))
    6232             :     {
    6233           1 :         CPLFree(pszWKT);
    6234           1 :         CSLDestroy(papszTO);
    6235           1 :         return nullptr;
    6236             :     }
    6237             : 
    6238             :     // Assign nodata values before the SetGeoTransform call.
    6239             :     // SetGeoTransform will trigger creation of the overview datasets for each
    6240             :     // zoom level and at that point the nodata value needs to be known.
    6241          19 :     int bHasNoData = FALSE;
    6242             :     double dfNoDataValue =
    6243          19 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    6244          19 :     if (eDT != GDT_UInt8 && bHasNoData)
    6245             :     {
    6246           3 :         poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
    6247             :     }
    6248             : 
    6249          19 :     poDS->SetGeoTransform(gt);
    6250          19 :     poDS->SetProjection(pszWKT);
    6251          19 :     CPLFree(pszWKT);
    6252          19 :     pszWKT = nullptr;
    6253          24 :     if (nTargetBands == 1 && nBands == 1 &&
    6254           5 :         poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    6255             :     {
    6256           2 :         poDS->GetRasterBand(1)->SetColorTable(
    6257           1 :             poSrcDS->GetRasterBand(1)->GetColorTable());
    6258             :     }
    6259             : 
    6260             :     hTransformArg =
    6261          19 :         GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
    6262          19 :     CSLDestroy(papszTO);
    6263          19 :     if (hTransformArg == nullptr)
    6264             :     {
    6265           0 :         return nullptr;
    6266             :     }
    6267             : 
    6268          19 :     poDS->SetMetadata(poSrcDS->GetMetadata());
    6269             : 
    6270             :     /* -------------------------------------------------------------------- */
    6271             :     /*      Warp the transformer with a linear approximator                 */
    6272             :     /* -------------------------------------------------------------------- */
    6273          19 :     hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
    6274             :                                                 hTransformArg, 0.125);
    6275          19 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
    6276             : 
    6277             :     /* -------------------------------------------------------------------- */
    6278             :     /*      Setup warp options.                                             */
    6279             :     /* -------------------------------------------------------------------- */
    6280          19 :     GDALWarpOptions *psWO = GDALCreateWarpOptions();
    6281             : 
    6282          19 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    6283          19 :     psWO->papszWarpOptions =
    6284          19 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    6285          19 :     if (bHasNoData)
    6286             :     {
    6287           3 :         if (dfNoDataValue == 0.0)
    6288             :         {
    6289             :             // Do not initialize in the case where nodata != 0, since we
    6290             :             // want the GeoPackage driver to return empty tiles at the nodata
    6291             :             // value instead of 0 as GDAL core would
    6292           0 :             psWO->papszWarpOptions =
    6293           0 :                 CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
    6294             :         }
    6295             : 
    6296           3 :         psWO->padfSrcNoDataReal =
    6297           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6298           3 :         psWO->padfSrcNoDataReal[0] = dfNoDataValue;
    6299             : 
    6300           3 :         psWO->padfDstNoDataReal =
    6301           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6302           3 :         psWO->padfDstNoDataReal[0] = dfNoDataValue;
    6303             :     }
    6304          19 :     psWO->eWorkingDataType = eDT;
    6305          19 :     psWO->eResampleAlg = eResampleAlg;
    6306             : 
    6307          19 :     psWO->hSrcDS = poSrcDS;
    6308          19 :     psWO->hDstDS = poDS.get();
    6309             : 
    6310          19 :     psWO->pfnTransformer = GDALApproxTransform;
    6311          19 :     psWO->pTransformerArg = hTransformArg;
    6312             : 
    6313          19 :     psWO->pfnProgress = pfnProgress;
    6314          19 :     psWO->pProgressArg = pProgressData;
    6315             : 
    6316             :     /* -------------------------------------------------------------------- */
    6317             :     /*      Setup band mapping.                                             */
    6318             :     /* -------------------------------------------------------------------- */
    6319             : 
    6320          19 :     if (nBands == 2 || nBands == 4)
    6321           1 :         psWO->nBandCount = nBands - 1;
    6322             :     else
    6323          18 :         psWO->nBandCount = nBands;
    6324             : 
    6325          19 :     psWO->panSrcBands =
    6326          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6327          19 :     psWO->panDstBands =
    6328          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6329             : 
    6330          46 :     for (int i = 0; i < psWO->nBandCount; i++)
    6331             :     {
    6332          27 :         psWO->panSrcBands[i] = i + 1;
    6333          27 :         psWO->panDstBands[i] = i + 1;
    6334             :     }
    6335             : 
    6336          19 :     if (nBands == 2 || nBands == 4)
    6337             :     {
    6338           1 :         psWO->nSrcAlphaBand = nBands;
    6339             :     }
    6340          19 :     if (nTargetBands == 2 || nTargetBands == 4)
    6341             :     {
    6342          13 :         psWO->nDstAlphaBand = nTargetBands;
    6343             :     }
    6344             : 
    6345             :     /* -------------------------------------------------------------------- */
    6346             :     /*      Initialize and execute the warp.                                */
    6347             :     /* -------------------------------------------------------------------- */
    6348          38 :     GDALWarpOperation oWO;
    6349             : 
    6350          19 :     CPLErr eErr = oWO.Initialize(psWO);
    6351          19 :     if (eErr == CE_None)
    6352             :     {
    6353             :         /*if( bMulti )
    6354             :             eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
    6355             :         else*/
    6356          19 :         eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
    6357             :     }
    6358          19 :     if (eErr != CE_None)
    6359             :     {
    6360           0 :         poDS.reset();
    6361             :     }
    6362             : 
    6363          19 :     GDALDestroyTransformer(hTransformArg);
    6364          19 :     GDALDestroyWarpOptions(psWO);
    6365             : 
    6366          19 :     if (poDS)
    6367          19 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6368             : 
    6369          19 :     return poDS.release();
    6370             : }
    6371             : 
    6372             : /************************************************************************/
    6373             : /*                      ParseCompressionOptions()                       */
    6374             : /************************************************************************/
    6375             : 
    6376         476 : void GDALGeoPackageDataset::ParseCompressionOptions(CSLConstList papszOptions)
    6377             : {
    6378         476 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6379         476 :     if (pszZLevel)
    6380           0 :         m_nZLevel = atoi(pszZLevel);
    6381             : 
    6382         476 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6383         476 :     if (pszQuality)
    6384           0 :         m_nQuality = atoi(pszQuality);
    6385             : 
    6386         476 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6387         476 :     if (pszDither)
    6388           0 :         m_bDither = CPLTestBool(pszDither);
    6389         476 : }
    6390             : 
    6391             : /************************************************************************/
    6392             : /*                       RegisterWebPExtension()                        */
    6393             : /************************************************************************/
    6394             : 
    6395          11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
    6396             : {
    6397          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6398           0 :         return false;
    6399             : 
    6400          11 :     char *pszSQL = sqlite3_mprintf(
    6401             :         "INSERT INTO gpkg_extensions "
    6402             :         "(table_name, column_name, extension_name, definition, scope) "
    6403             :         "VALUES "
    6404             :         "('%q', 'tile_data', 'gpkg_webp', "
    6405             :         "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
    6406             :         "'read-write')",
    6407             :         m_osRasterTable.c_str());
    6408          11 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6409          11 :     sqlite3_free(pszSQL);
    6410             : 
    6411          11 :     return OGRERR_NONE == eErr;
    6412             : }
    6413             : 
    6414             : /************************************************************************/
    6415             : /*                     RegisterZoomOtherExtension()                     */
    6416             : /************************************************************************/
    6417             : 
    6418           1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
    6419             : {
    6420           1 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6421           0 :         return false;
    6422             : 
    6423           1 :     char *pszSQL = sqlite3_mprintf(
    6424             :         "INSERT INTO gpkg_extensions "
    6425             :         "(table_name, column_name, extension_name, definition, scope) "
    6426             :         "VALUES "
    6427             :         "('%q', 'tile_data', 'gpkg_zoom_other', "
    6428             :         "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
    6429             :         "'read-write')",
    6430             :         m_osRasterTable.c_str());
    6431           1 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6432           1 :     sqlite3_free(pszSQL);
    6433           1 :     return OGRERR_NONE == eErr;
    6434             : }
    6435             : 
    6436             : /************************************************************************/
    6437             : /*                              GetLayer()                              */
    6438             : /************************************************************************/
    6439             : 
    6440       17210 : const OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) const
    6441             : 
    6442             : {
    6443       17210 :     if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
    6444           7 :         return nullptr;
    6445             :     else
    6446       17203 :         return m_apoLayers[iLayer].get();
    6447             : }
    6448             : 
    6449             : /************************************************************************/
    6450             : /*                            LaunderName()                             */
    6451             : /************************************************************************/
    6452             : 
    6453             : /** Launder identifiers (table, column names) according to guidance at
    6454             :  * https://www.geopackage.org/guidance/getting-started.html:
    6455             :  * "For maximum interoperability, start your database identifiers (table names,
    6456             :  * column names, etc.) with a lowercase character and only use lowercase
    6457             :  * characters, numbers 0-9, and underscores (_)."
    6458             :  */
    6459             : 
    6460             : /* static */
    6461           5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
    6462             : {
    6463           5 :     char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
    6464          10 :     const std::string osStrASCII(pszASCII);
    6465           5 :     CPLFree(pszASCII);
    6466             : 
    6467          10 :     std::string osRet;
    6468           5 :     osRet.reserve(osStrASCII.size());
    6469             : 
    6470          29 :     for (size_t i = 0; i < osStrASCII.size(); ++i)
    6471             :     {
    6472          24 :         if (osRet.empty())
    6473             :         {
    6474           5 :             if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6475             :             {
    6476           2 :                 osRet += (osStrASCII[i] - 'A' + 'a');
    6477             :             }
    6478           3 :             else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
    6479             :             {
    6480           2 :                 osRet += osStrASCII[i];
    6481             :             }
    6482             :             else
    6483             :             {
    6484           1 :                 continue;
    6485             :             }
    6486             :         }
    6487          19 :         else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6488             :         {
    6489          11 :             osRet += (osStrASCII[i] - 'A' + 'a');
    6490             :         }
    6491           9 :         else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
    6492          14 :                  (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
    6493           5 :                  osStrASCII[i] == '_')
    6494             :         {
    6495           7 :             osRet += osStrASCII[i];
    6496             :         }
    6497             :         else
    6498             :         {
    6499           1 :             osRet += '_';
    6500             :         }
    6501             :     }
    6502             : 
    6503           5 :     if (osRet.empty() && !osStrASCII.empty())
    6504           2 :         return LaunderName(std::string("x").append(osStrASCII));
    6505             : 
    6506           4 :     if (osRet != osStr)
    6507             :     {
    6508           3 :         CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
    6509             :                  osRet.c_str());
    6510             :     }
    6511             : 
    6512           4 :     return osRet;
    6513             : }
    6514             : 
    6515             : /************************************************************************/
    6516             : /*                            ICreateLayer()                            */
    6517             : /************************************************************************/
    6518             : 
    6519             : OGRLayer *
    6520        1030 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6521             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6522             :                                     CSLConstList papszOptions)
    6523             : {
    6524             :     /* -------------------------------------------------------------------- */
    6525             :     /*      Verify we are in update mode.                                   */
    6526             :     /* -------------------------------------------------------------------- */
    6527        1030 :     if (!GetUpdate())
    6528             :     {
    6529           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    6530             :                  "Data source %s opened read-only.\n"
    6531             :                  "New layer %s cannot be created.\n",
    6532             :                  m_pszFilename, pszLayerName);
    6533             : 
    6534           0 :         return nullptr;
    6535             :     }
    6536             : 
    6537             :     const bool bLaunder =
    6538        1030 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6539             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6540        3090 :                                            : std::string(pszLayerName));
    6541             : 
    6542             :     const auto eGType =
    6543        1030 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6544             :     const auto poSpatialRef =
    6545        1030 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6546             : 
    6547        1030 :     if (!m_bHasGPKGGeometryColumns)
    6548             :     {
    6549           1 :         if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
    6550             :         {
    6551           0 :             return nullptr;
    6552             :         }
    6553           1 :         m_bHasGPKGGeometryColumns = true;
    6554             :     }
    6555             : 
    6556             :     // Check identifier unicity
    6557        1030 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6558        1030 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6559           0 :         pszIdentifier = nullptr;
    6560        1030 :     if (pszIdentifier != nullptr)
    6561             :     {
    6562          13 :         for (auto &poLayer : m_apoLayers)
    6563             :         {
    6564             :             const char *pszOtherIdentifier =
    6565           9 :                 poLayer->GetMetadataItem("IDENTIFIER");
    6566           9 :             if (pszOtherIdentifier == nullptr)
    6567           6 :                 pszOtherIdentifier = poLayer->GetName();
    6568          18 :             if (pszOtherIdentifier != nullptr &&
    6569          12 :                 EQUAL(pszOtherIdentifier, pszIdentifier) &&
    6570           3 :                 !EQUAL(poLayer->GetName(), osTableName.c_str()))
    6571             :             {
    6572           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6573             :                          "Identifier %s is already used by table %s",
    6574             :                          pszIdentifier, poLayer->GetName());
    6575           2 :                 return nullptr;
    6576             :             }
    6577             :         }
    6578             : 
    6579             :         // In case there would be table in gpkg_contents not listed as a
    6580             :         // vector layer
    6581           4 :         char *pszSQL = sqlite3_mprintf(
    6582             :             "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
    6583             :             "LIMIT 2",
    6584             :             pszIdentifier);
    6585           4 :         auto oResult = SQLQuery(hDB, pszSQL);
    6586           4 :         sqlite3_free(pszSQL);
    6587           8 :         if (oResult && oResult->RowCount() > 0 &&
    6588           9 :             oResult->GetValue(0, 0) != nullptr &&
    6589           1 :             !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
    6590             :         {
    6591           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6592             :                      "Identifier %s is already used by table %s", pszIdentifier,
    6593             :                      oResult->GetValue(0, 0));
    6594           1 :             return nullptr;
    6595             :         }
    6596             :     }
    6597             : 
    6598             :     /* Read GEOMETRY_NAME option */
    6599             :     const char *pszGeomColumnName =
    6600        1027 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6601        1027 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6602         933 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6603        1027 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6604             :     {
    6605         851 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6606         851 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6607         816 :             pszGeomColumnName = nullptr;
    6608             :     }
    6609        1027 :     if (pszGeomColumnName == nullptr)
    6610         898 :         pszGeomColumnName = "geom";
    6611             :     const bool bGeomNullable =
    6612        1027 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6613             : 
    6614             :     /* Read FID option */
    6615        1027 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6616        1027 :     if (pszFIDColumnName == nullptr)
    6617         929 :         pszFIDColumnName = "fid";
    6618             : 
    6619        1027 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6620             :     {
    6621        1027 :         if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
    6622             :         {
    6623           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    6624             :                      "The primary key (%s) name may not contain special "
    6625             :                      "characters or spaces",
    6626             :                      pszFIDColumnName);
    6627           2 :             return nullptr;
    6628             :         }
    6629             : 
    6630             :         /* Avoiding gpkg prefixes is not an official requirement, but seems wise
    6631             :          */
    6632        1025 :         if (STARTS_WITH(osTableName.c_str(), "gpkg"))
    6633             :         {
    6634           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6635             :                      "The layer name may not begin with 'gpkg' as it is a "
    6636             :                      "reserved geopackage prefix");
    6637           0 :             return nullptr;
    6638             :         }
    6639             : 
    6640             :         /* Preemptively try and avoid sqlite3 syntax errors due to  */
    6641             :         /* illegal characters. */
    6642        1025 :         if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
    6643             :             0)
    6644             :         {
    6645           0 :             CPLError(
    6646             :                 CE_Failure, CPLE_AppDefined,
    6647             :                 "The layer name may not contain special characters or spaces");
    6648           0 :             return nullptr;
    6649             :         }
    6650             :     }
    6651             : 
    6652             :     /* Check for any existing layers that already use this name */
    6653        1268 :     for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
    6654             :          iLayer++)
    6655             :     {
    6656         244 :         if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
    6657             :         {
    6658             :             const char *pszOverwrite =
    6659           2 :                 CSLFetchNameValue(papszOptions, "OVERWRITE");
    6660           2 :             if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
    6661             :             {
    6662           1 :                 DeleteLayer(iLayer);
    6663             :             }
    6664             :             else
    6665             :             {
    6666           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6667             :                          "Layer %s already exists, CreateLayer failed.\n"
    6668             :                          "Use the layer creation option OVERWRITE=YES to "
    6669             :                          "replace it.",
    6670             :                          osTableName.c_str());
    6671           1 :                 return nullptr;
    6672             :             }
    6673             :         }
    6674             :     }
    6675             : 
    6676        1024 :     if (m_apoLayers.size() == 1)
    6677             :     {
    6678             :         // Async RTree building doesn't play well with multiple layer:
    6679             :         // SQLite3 locks being hold for a long time, random failed commits,
    6680             :         // etc.
    6681          95 :         m_apoLayers[0]->FinishOrDisableThreadedRTree();
    6682             :     }
    6683             : 
    6684             :     /* Create a blank layer. */
    6685             :     auto poLayer =
    6686        2048 :         std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
    6687             : 
    6688        2048 :     OGRSpatialReferenceRefCountedPtr poSRS;
    6689        1024 :     if (poSpatialRef)
    6690             :     {
    6691         343 :         poSRS = OGRSpatialReferenceRefCountedPtr::makeClone(poSpatialRef);
    6692         343 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6693             :     }
    6694        3073 :     poLayer->SetCreationParameters(
    6695             :         eGType,
    6696        1025 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6697        1024 :         bGeomNullable, poSRS.get(), CSLFetchNameValue(papszOptions, "SRID"),
    6698        2048 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6699             :                            : OGRGeomCoordinatePrecision(),
    6700        1024 :         CPLTestBool(
    6701             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6702        1024 :         CPLTestBool(CSLFetchNameValueDef(
    6703             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6704        1025 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6705             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6706             : 
    6707        1024 :     poLayer->SetLaunder(bLaunder);
    6708             : 
    6709             :     /* Should we create a spatial index ? */
    6710        1024 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6711        1024 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6712        1024 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6713             :     {
    6714         920 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6715             :     }
    6716             : 
    6717        1024 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6718        1024 :     poLayer->SetTruncateFieldsFlag(
    6719        1024 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6720        1024 :     if (eGType == wkbNone)
    6721             :     {
    6722          82 :         const char *pszASpatialVariant = CSLFetchNameValueDef(
    6723             :             papszOptions, "ASPATIAL_VARIANT",
    6724          82 :             m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
    6725             :                 ? "NOT_REGISTERED"
    6726             :                 : "GPKG_ATTRIBUTES");
    6727          82 :         GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
    6728          82 :         if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
    6729          70 :             eASpatialVariant = GPKG_ATTRIBUTES;
    6730          12 :         else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
    6731             :         {
    6732           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6733             :                      "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
    6734           0 :             return nullptr;
    6735             :         }
    6736          12 :         else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
    6737          12 :             eASpatialVariant = NOT_REGISTERED;
    6738             :         else
    6739             :         {
    6740           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6741             :                      "Unsupported value for ASPATIAL_VARIANT: %s",
    6742             :                      pszASpatialVariant);
    6743           0 :             return nullptr;
    6744             :         }
    6745          82 :         poLayer->SetASpatialVariant(eASpatialVariant);
    6746             :     }
    6747             : 
    6748             :     const char *pszDateTimePrecision =
    6749        1024 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6750        1024 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6751             :     {
    6752           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6753             :     }
    6754        1022 :     else if (EQUAL(pszDateTimePrecision, "SECOND"))
    6755             :     {
    6756           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6757           0 :             CPLError(
    6758             :                 CE_Warning, CPLE_AppDefined,
    6759             :                 "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
    6760           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
    6761             :     }
    6762        1021 :     else if (EQUAL(pszDateTimePrecision, "MINUTE"))
    6763             :     {
    6764           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6765           0 :             CPLError(
    6766             :                 CE_Warning, CPLE_AppDefined,
    6767             :                 "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
    6768           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
    6769             :     }
    6770        1020 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6771             :     {
    6772        1019 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6773          13 :             poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6774             :     }
    6775             :     else
    6776             :     {
    6777           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6778             :                  "Unsupported value for DATETIME_PRECISION: %s",
    6779             :                  pszDateTimePrecision);
    6780           1 :         return nullptr;
    6781             :     }
    6782             : 
    6783             :     // If there was an ogr_empty_table table, we can remove it
    6784             :     // But do it at dataset closing, otherwise locking performance issues
    6785             :     // can arise (probably when transactions are used).
    6786        1023 :     m_bRemoveOGREmptyTable = true;
    6787             : 
    6788        1023 :     m_apoLayers.emplace_back(std::move(poLayer));
    6789        1023 :     return m_apoLayers.back().get();
    6790             : }
    6791             : 
    6792             : /************************************************************************/
    6793             : /*                           FindLayerIndex()                           */
    6794             : /************************************************************************/
    6795             : 
    6796          29 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
    6797             : 
    6798             : {
    6799          51 :     for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
    6800             :          iLayer++)
    6801             :     {
    6802          35 :         if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
    6803          13 :             return iLayer;
    6804             :     }
    6805          16 :     return -1;
    6806             : }
    6807             : 
    6808             : /************************************************************************/
    6809             : /*                         DeleteLayerCommon()                          */
    6810             : /************************************************************************/
    6811             : 
    6812          43 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    6813             : {
    6814             :     // Temporary remove foreign key checks
    6815             :     const GPKGTemporaryForeignKeyCheckDisabler
    6816          43 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6817             : 
    6818          43 :     char *pszSQL = sqlite3_mprintf(
    6819             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    6820             :         pszLayerName);
    6821          43 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    6822          43 :     sqlite3_free(pszSQL);
    6823             : 
    6824          43 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    6825             :     {
    6826          41 :         pszSQL = sqlite3_mprintf(
    6827             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    6828             :             pszLayerName);
    6829          41 :         eErr = SQLCommand(hDB, pszSQL);
    6830          41 :         sqlite3_free(pszSQL);
    6831             :     }
    6832             : 
    6833          43 :     if (eErr == OGRERR_NONE && HasMetadataTables())
    6834             :     {
    6835             :         // Delete from gpkg_metadata metadata records that are only referenced
    6836             :         // by the table we are about to drop
    6837          12 :         pszSQL = sqlite3_mprintf(
    6838             :             "DELETE FROM gpkg_metadata WHERE id IN ("
    6839             :             "SELECT DISTINCT md_file_id FROM "
    6840             :             "gpkg_metadata_reference WHERE "
    6841             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6842             :             "AND id NOT IN ("
    6843             :             "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
    6844             :             "md_file_id IN (SELECT DISTINCT md_file_id FROM "
    6845             :             "gpkg_metadata_reference WHERE "
    6846             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6847             :             "AND lower(table_name) <> lower('%q'))",
    6848             :             pszLayerName, pszLayerName, pszLayerName);
    6849          12 :         eErr = SQLCommand(hDB, pszSQL);
    6850          12 :         sqlite3_free(pszSQL);
    6851             : 
    6852          12 :         if (eErr == OGRERR_NONE)
    6853             :         {
    6854             :             pszSQL =
    6855          12 :                 sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
    6856             :                                 "lower(table_name) = lower('%q')",
    6857             :                                 pszLayerName);
    6858          12 :             eErr = SQLCommand(hDB, pszSQL);
    6859          12 :             sqlite3_free(pszSQL);
    6860             :         }
    6861             :     }
    6862             : 
    6863          43 :     if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
    6864             :     {
    6865             :         // Remove reference to potential corresponding mapping table in
    6866             :         // gpkg_extensions
    6867           4 :         pszSQL = sqlite3_mprintf(
    6868             :             "DELETE FROM gpkg_extensions WHERE "
    6869             :             "extension_name IN ('related_tables', "
    6870             :             "'gpkg_related_tables') AND lower(table_name) = "
    6871             :             "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
    6872             :             "lower(base_table_name) = lower('%q') OR "
    6873             :             "lower(related_table_name) = lower('%q') OR "
    6874             :             "lower(mapping_table_name) = lower('%q'))",
    6875             :             pszLayerName, pszLayerName, pszLayerName);
    6876           4 :         eErr = SQLCommand(hDB, pszSQL);
    6877           4 :         sqlite3_free(pszSQL);
    6878             : 
    6879           4 :         if (eErr == OGRERR_NONE)
    6880             :         {
    6881             :             // Remove reference to potential corresponding mapping table in
    6882             :             // gpkgext_relations
    6883             :             pszSQL =
    6884           4 :                 sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
    6885             :                                 "lower(base_table_name) = lower('%q') OR "
    6886             :                                 "lower(related_table_name) = lower('%q') OR "
    6887             :                                 "lower(mapping_table_name) = lower('%q')",
    6888             :                                 pszLayerName, pszLayerName, pszLayerName);
    6889           4 :             eErr = SQLCommand(hDB, pszSQL);
    6890           4 :             sqlite3_free(pszSQL);
    6891             :         }
    6892             : 
    6893           4 :         if (eErr == OGRERR_NONE && HasExtensionsTable())
    6894             :         {
    6895             :             // If there is no longer any mapping table, then completely
    6896             :             // remove any reference to the extension in gpkg_extensions
    6897             :             // as mandated per the related table specification.
    6898             :             OGRErr err;
    6899           4 :             if (SQLGetInteger(hDB,
    6900             :                               "SELECT COUNT(*) FROM gpkg_extensions WHERE "
    6901             :                               "extension_name IN ('related_tables', "
    6902             :                               "'gpkg_related_tables') AND "
    6903             :                               "lower(table_name) != 'gpkgext_relations'",
    6904           4 :                               &err) == 0)
    6905             :             {
    6906           2 :                 eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
    6907             :                                        "extension_name IN ('related_tables', "
    6908             :                                        "'gpkg_related_tables')");
    6909             :             }
    6910             : 
    6911           4 :             ClearCachedRelationships();
    6912             :         }
    6913             :     }
    6914             : 
    6915          43 :     if (eErr == OGRERR_NONE)
    6916             :     {
    6917          43 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    6918          43 :         eErr = SQLCommand(hDB, pszSQL);
    6919          43 :         sqlite3_free(pszSQL);
    6920             :     }
    6921             : 
    6922             :     // Check foreign key integrity
    6923          43 :     if (eErr == OGRERR_NONE)
    6924             :     {
    6925          43 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    6926             :     }
    6927             : 
    6928          86 :     return eErr;
    6929             : }
    6930             : 
    6931             : /************************************************************************/
    6932             : /*                            DeleteLayer()                             */
    6933             : /************************************************************************/
    6934             : 
    6935          40 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    6936             : {
    6937          79 :     if (!GetUpdate() || iLayer < 0 ||
    6938          39 :         iLayer >= static_cast<int>(m_apoLayers.size()))
    6939           2 :         return OGRERR_FAILURE;
    6940             : 
    6941          38 :     m_apoLayers[iLayer]->ResetReading();
    6942          38 :     m_apoLayers[iLayer]->SyncToDisk();
    6943             : 
    6944          76 :     CPLString osLayerName = m_apoLayers[iLayer]->GetName();
    6945             : 
    6946          38 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    6947             : 
    6948             :     // Temporary remove foreign key checks
    6949             :     const GPKGTemporaryForeignKeyCheckDisabler
    6950          38 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6951             : 
    6952          38 :     OGRErr eErr = SoftStartTransaction();
    6953             : 
    6954          38 :     if (eErr == OGRERR_NONE)
    6955             :     {
    6956          38 :         if (m_apoLayers[iLayer]->HasSpatialIndex())
    6957          35 :             m_apoLayers[iLayer]->DropSpatialIndex();
    6958             : 
    6959             :         char *pszSQL =
    6960          38 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    6961             :                             "lower(table_name) = lower('%q')",
    6962             :                             osLayerName.c_str());
    6963          38 :         eErr = SQLCommand(hDB, pszSQL);
    6964          38 :         sqlite3_free(pszSQL);
    6965             :     }
    6966             : 
    6967          38 :     if (eErr == OGRERR_NONE && HasDataColumnsTable())
    6968             :     {
    6969           1 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
    6970             :                                        "lower(table_name) = lower('%q')",
    6971             :                                        osLayerName.c_str());
    6972           1 :         eErr = SQLCommand(hDB, pszSQL);
    6973           1 :         sqlite3_free(pszSQL);
    6974             :     }
    6975             : 
    6976             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    6977          38 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    6978             :     {
    6979          38 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    6980             :                                        "lower(table_name) = lower('%q')",
    6981             :                                        osLayerName.c_str());
    6982          38 :         eErr = SQLCommand(hDB, pszSQL);
    6983          38 :         sqlite3_free(pszSQL);
    6984             :     }
    6985             : #endif
    6986             : 
    6987          38 :     if (eErr == OGRERR_NONE)
    6988             :     {
    6989          38 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    6990             :     }
    6991             : 
    6992          38 :     if (eErr == OGRERR_NONE)
    6993             :     {
    6994          38 :         eErr = SoftCommitTransaction();
    6995          38 :         if (eErr == OGRERR_NONE)
    6996             :         {
    6997             :             /* Delete the layer object */
    6998          38 :             m_apoLayers.erase(m_apoLayers.begin() + iLayer);
    6999             :         }
    7000             :     }
    7001             :     else
    7002             :     {
    7003           0 :         SoftRollbackTransaction();
    7004             :     }
    7005             : 
    7006          38 :     return eErr;
    7007             : }
    7008             : 
    7009             : /************************************************************************/
    7010             : /*                         DeleteRasterLayer()                          */
    7011             : /************************************************************************/
    7012             : 
    7013           2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
    7014             : {
    7015             :     // Temporary remove foreign key checks
    7016             :     const GPKGTemporaryForeignKeyCheckDisabler
    7017           2 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7018             : 
    7019           2 :     OGRErr eErr = SoftStartTransaction();
    7020             : 
    7021           2 :     if (eErr == OGRERR_NONE)
    7022             :     {
    7023           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
    7024             :                                        "lower(table_name) = lower('%q')",
    7025             :                                        pszLayerName);
    7026           2 :         eErr = SQLCommand(hDB, pszSQL);
    7027           2 :         sqlite3_free(pszSQL);
    7028             :     }
    7029             : 
    7030           2 :     if (eErr == OGRERR_NONE)
    7031             :     {
    7032           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
    7033             :                                        "lower(table_name) = lower('%q')",
    7034             :                                        pszLayerName);
    7035           2 :         eErr = SQLCommand(hDB, pszSQL);
    7036           2 :         sqlite3_free(pszSQL);
    7037             :     }
    7038             : 
    7039           2 :     if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
    7040             :     {
    7041             :         char *pszSQL =
    7042           1 :             sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
    7043             :                             "WHERE lower(tile_matrix_set_name) = lower('%q')",
    7044             :                             pszLayerName);
    7045           1 :         eErr = SQLCommand(hDB, pszSQL);
    7046           1 :         sqlite3_free(pszSQL);
    7047             : 
    7048           1 :         if (eErr == OGRERR_NONE)
    7049             :         {
    7050             :             pszSQL =
    7051           1 :                 sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
    7052             :                                 "WHERE lower(tpudt_name) = lower('%q')",
    7053             :                                 pszLayerName);
    7054           1 :             eErr = SQLCommand(hDB, pszSQL);
    7055           1 :             sqlite3_free(pszSQL);
    7056             :         }
    7057             :     }
    7058             : 
    7059           2 :     if (eErr == OGRERR_NONE)
    7060             :     {
    7061           2 :         eErr = DeleteLayerCommon(pszLayerName);
    7062             :     }
    7063             : 
    7064           2 :     if (eErr == OGRERR_NONE)
    7065             :     {
    7066           2 :         eErr = SoftCommitTransaction();
    7067             :     }
    7068             :     else
    7069             :     {
    7070           0 :         SoftRollbackTransaction();
    7071             :     }
    7072             : 
    7073           4 :     return eErr;
    7074             : }
    7075             : 
    7076             : /************************************************************************/
    7077             : /*                     DeleteVectorOrRasterLayer()                      */
    7078             : /************************************************************************/
    7079             : 
    7080          13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
    7081             : {
    7082             : 
    7083          13 :     int idx = FindLayerIndex(pszLayerName);
    7084          13 :     if (idx >= 0)
    7085             :     {
    7086           5 :         DeleteLayer(idx);
    7087           5 :         return true;
    7088             :     }
    7089             : 
    7090             :     char *pszSQL =
    7091           8 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7092             :                         "lower(table_name) = lower('%q') "
    7093             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7094             :                         pszLayerName);
    7095           8 :     bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7096           8 :     sqlite3_free(pszSQL);
    7097           8 :     if (bIsRasterTable)
    7098             :     {
    7099           2 :         DeleteRasterLayer(pszLayerName);
    7100           2 :         return true;
    7101             :     }
    7102           6 :     return false;
    7103             : }
    7104             : 
    7105           7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
    7106             :     const char *pszLayerName, const char *pszNewLayerName)
    7107             : {
    7108           7 :     int idx = FindLayerIndex(pszLayerName);
    7109           7 :     if (idx >= 0)
    7110             :     {
    7111           4 :         m_apoLayers[idx]->Rename(pszNewLayerName);
    7112           4 :         return true;
    7113             :     }
    7114             : 
    7115             :     char *pszSQL =
    7116           3 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7117             :                         "lower(table_name) = lower('%q') "
    7118             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7119             :                         pszLayerName);
    7120           3 :     const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7121           3 :     sqlite3_free(pszSQL);
    7122             : 
    7123           3 :     if (bIsRasterTable)
    7124             :     {
    7125           2 :         return RenameRasterLayer(pszLayerName, pszNewLayerName);
    7126             :     }
    7127             : 
    7128           1 :     return false;
    7129             : }
    7130             : 
    7131           2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
    7132             :                                               const char *pszNewLayerName)
    7133             : {
    7134           4 :     std::string osSQL;
    7135             : 
    7136           2 :     char *pszSQL = sqlite3_mprintf(
    7137             :         "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
    7138             :         "AND type IN ('table', 'view')",
    7139             :         pszNewLayerName);
    7140           2 :     const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
    7141           2 :     sqlite3_free(pszSQL);
    7142           2 :     if (bAlreadyExists)
    7143             :     {
    7144           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
    7145             :                  pszNewLayerName);
    7146           0 :         return false;
    7147             :     }
    7148             : 
    7149             :     // Temporary remove foreign key checks
    7150             :     const GPKGTemporaryForeignKeyCheckDisabler
    7151           4 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7152             : 
    7153           2 :     if (SoftStartTransaction() != OGRERR_NONE)
    7154             :     {
    7155           0 :         return false;
    7156             :     }
    7157             : 
    7158           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
    7159             :                              "lower(table_name) = lower('%q');",
    7160             :                              pszNewLayerName, pszLayerName);
    7161           2 :     osSQL = pszSQL;
    7162           2 :     sqlite3_free(pszSQL);
    7163             : 
    7164           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
    7165             :                              "lower(identifier) = lower('%q');",
    7166             :                              pszNewLayerName, pszLayerName);
    7167           2 :     osSQL += pszSQL;
    7168           2 :     sqlite3_free(pszSQL);
    7169             : 
    7170             :     pszSQL =
    7171           2 :         sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
    7172             :                         "lower(table_name) = lower('%q');",
    7173             :                         pszNewLayerName, pszLayerName);
    7174           2 :     osSQL += pszSQL;
    7175           2 :     sqlite3_free(pszSQL);
    7176             : 
    7177           2 :     pszSQL = sqlite3_mprintf(
    7178             :         "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
    7179             :         "lower(table_name) = lower('%q');",
    7180             :         pszNewLayerName, pszLayerName);
    7181           2 :     osSQL += pszSQL;
    7182           2 :     sqlite3_free(pszSQL);
    7183             : 
    7184           2 :     if (HasGriddedCoverageAncillaryTable())
    7185             :     {
    7186           1 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
    7187             :                                  "SET tile_matrix_set_name = '%q' WHERE "
    7188             :                                  "lower(tile_matrix_set_name) = lower('%q');",
    7189             :                                  pszNewLayerName, pszLayerName);
    7190           1 :         osSQL += pszSQL;
    7191           1 :         sqlite3_free(pszSQL);
    7192             : 
    7193           1 :         pszSQL = sqlite3_mprintf(
    7194             :             "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
    7195             :             "lower(tpudt_name) = lower('%q');",
    7196             :             pszNewLayerName, pszLayerName);
    7197           1 :         osSQL += pszSQL;
    7198           1 :         sqlite3_free(pszSQL);
    7199             :     }
    7200             : 
    7201           2 :     if (HasExtensionsTable())
    7202             :     {
    7203           2 :         pszSQL = sqlite3_mprintf(
    7204             :             "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
    7205             :             "lower(table_name) = lower('%q');",
    7206             :             pszNewLayerName, pszLayerName);
    7207           2 :         osSQL += pszSQL;
    7208           2 :         sqlite3_free(pszSQL);
    7209             :     }
    7210             : 
    7211           2 :     if (HasMetadataTables())
    7212             :     {
    7213           1 :         pszSQL = sqlite3_mprintf(
    7214             :             "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
    7215             :             "lower(table_name) = lower('%q');",
    7216             :             pszNewLayerName, pszLayerName);
    7217           1 :         osSQL += pszSQL;
    7218           1 :         sqlite3_free(pszSQL);
    7219             :     }
    7220             : 
    7221           2 :     if (HasDataColumnsTable())
    7222             :     {
    7223           0 :         pszSQL = sqlite3_mprintf(
    7224             :             "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
    7225             :             "lower(table_name) = lower('%q');",
    7226             :             pszNewLayerName, pszLayerName);
    7227           0 :         osSQL += pszSQL;
    7228           0 :         sqlite3_free(pszSQL);
    7229             :     }
    7230             : 
    7231           2 :     if (HasQGISLayerStyles())
    7232             :     {
    7233             :         // Update QGIS styles
    7234             :         pszSQL =
    7235           0 :             sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
    7236             :                             "lower(f_table_name) = lower('%q');",
    7237             :                             pszNewLayerName, pszLayerName);
    7238           0 :         osSQL += pszSQL;
    7239           0 :         sqlite3_free(pszSQL);
    7240             :     }
    7241             : 
    7242             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7243           2 :     if (m_bHasGPKGOGRContents)
    7244             :     {
    7245           2 :         pszSQL = sqlite3_mprintf(
    7246             :             "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
    7247             :             "lower(table_name) = lower('%q');",
    7248             :             pszNewLayerName, pszLayerName);
    7249           2 :         osSQL += pszSQL;
    7250           2 :         sqlite3_free(pszSQL);
    7251             :     }
    7252             : #endif
    7253             : 
    7254           2 :     if (HasGpkgextRelationsTable())
    7255             :     {
    7256           0 :         pszSQL = sqlite3_mprintf(
    7257             :             "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
    7258             :             "lower(base_table_name) = lower('%q');",
    7259             :             pszNewLayerName, pszLayerName);
    7260           0 :         osSQL += pszSQL;
    7261           0 :         sqlite3_free(pszSQL);
    7262             : 
    7263           0 :         pszSQL = sqlite3_mprintf(
    7264             :             "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
    7265             :             "lower(related_table_name) = lower('%q');",
    7266             :             pszNewLayerName, pszLayerName);
    7267           0 :         osSQL += pszSQL;
    7268           0 :         sqlite3_free(pszSQL);
    7269             : 
    7270           0 :         pszSQL = sqlite3_mprintf(
    7271             :             "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
    7272             :             "lower(mapping_table_name) = lower('%q');",
    7273             :             pszNewLayerName, pszLayerName);
    7274           0 :         osSQL += pszSQL;
    7275           0 :         sqlite3_free(pszSQL);
    7276             :     }
    7277             : 
    7278             :     // Drop all triggers for the layer
    7279           2 :     pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
    7280             :                              "'trigger' AND tbl_name = '%q'",
    7281             :                              pszLayerName);
    7282           2 :     auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
    7283           2 :     sqlite3_free(pszSQL);
    7284           2 :     if (oTriggerResult)
    7285             :     {
    7286          14 :         for (int i = 0; i < oTriggerResult->RowCount(); i++)
    7287             :         {
    7288          12 :             const char *pszTriggerName = oTriggerResult->GetValue(0, i);
    7289          12 :             pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
    7290             :                                      pszTriggerName);
    7291          12 :             osSQL += pszSQL;
    7292          12 :             sqlite3_free(pszSQL);
    7293             :         }
    7294             :     }
    7295             : 
    7296           2 :     pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
    7297             :                              pszLayerName, pszNewLayerName);
    7298           2 :     osSQL += pszSQL;
    7299           2 :     sqlite3_free(pszSQL);
    7300             : 
    7301             :     // Recreate all zoom/tile triggers
    7302           2 :     if (oTriggerResult)
    7303             :     {
    7304           2 :         osSQL += CreateRasterTriggersSQL(pszNewLayerName);
    7305             :     }
    7306             : 
    7307           2 :     OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
    7308             : 
    7309             :     // Check foreign key integrity
    7310           2 :     if (eErr == OGRERR_NONE)
    7311             :     {
    7312           2 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7313             :     }
    7314             : 
    7315           2 :     if (eErr == OGRERR_NONE)
    7316             :     {
    7317           2 :         eErr = SoftCommitTransaction();
    7318             :     }
    7319             :     else
    7320             :     {
    7321           0 :         SoftRollbackTransaction();
    7322             :     }
    7323             : 
    7324           2 :     return eErr == OGRERR_NONE;
    7325             : }
    7326             : 
    7327             : /************************************************************************/
    7328             : /*                           TestCapability()                           */
    7329             : /************************************************************************/
    7330             : 
    7331         615 : int GDALGeoPackageDataset::TestCapability(const char *pszCap) const
    7332             : {
    7333         615 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7334         402 :         EQUAL(pszCap, "RenameLayer"))
    7335             :     {
    7336         213 :         return GetUpdate();
    7337             :     }
    7338         402 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7339          12 :         return TRUE;
    7340         390 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7341           8 :         return TRUE;
    7342         382 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7343           8 :         return TRUE;
    7344         374 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7345         374 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7346         374 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7347         374 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7348         374 :              EQUAL(pszCap, ODsCAddFieldDomain) ||
    7349         372 :              EQUAL(pszCap, ODsCUpdateFieldDomain) ||
    7350         370 :              EQUAL(pszCap, ODsCDeleteFieldDomain))
    7351             :     {
    7352           6 :         return GetUpdate();
    7353             :     }
    7354             : 
    7355         368 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7356             : }
    7357             : 
    7358             : /************************************************************************/
    7359             : /*                       ResetReadingAllLayers()                        */
    7360             : /************************************************************************/
    7361             : 
    7362         205 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7363             : {
    7364         415 :     for (auto &poLayer : m_apoLayers)
    7365             :     {
    7366         210 :         poLayer->ResetReading();
    7367             :     }
    7368         205 : }
    7369             : 
    7370             : /************************************************************************/
    7371             : /*                             ExecuteSQL()                             */
    7372             : /************************************************************************/
    7373             : 
    7374             : static const char *const apszFuncsWithSideEffects[] = {
    7375             :     "CreateSpatialIndex",
    7376             :     "DisableSpatialIndex",
    7377             :     "HasSpatialIndex",
    7378             :     "RegisterGeometryExtension",
    7379             : };
    7380             : 
    7381        5720 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7382             :                                             OGRGeometry *poSpatialFilter,
    7383             :                                             const char *pszDialect)
    7384             : 
    7385             : {
    7386        5720 :     m_bHasReadMetadataFromStorage = false;
    7387             : 
    7388        5720 :     FlushMetadata();
    7389             : 
    7390        5738 :     while (*pszSQLCommand != '\0' &&
    7391        5738 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7392          18 :         pszSQLCommand++;
    7393             : 
    7394       11440 :     CPLString osSQLCommand(pszSQLCommand);
    7395        5720 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7396          48 :         osSQLCommand.pop_back();
    7397             : 
    7398       11439 :     if (osSQLCommand.ifind("AsGPB(ST_") != std::string::npos ||
    7399        5719 :         osSQLCommand.ifind("AsGPB( ST_") != std::string::npos)
    7400             :     {
    7401           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    7402             :                  "Use of AsGPB(ST_xxx(...)) found in \"%s\". Since GDAL 3.13, "
    7403             :                  "ST_xxx() functions return a GeoPackage geometry when used "
    7404             :                  "with a GeoPackage connection, and the use of AsGPB() is no "
    7405             :                  "longer needed. It is here automatically removed",
    7406             :                  osSQLCommand.c_str());
    7407           1 :         osSQLCommand.replaceAll("AsGPB(ST_", "(ST_");
    7408           1 :         osSQLCommand.replaceAll("AsGPB( ST_", "(ST_");
    7409             :     }
    7410             : 
    7411        5720 :     if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
    7412             :     {
    7413             :         // Some SQL commands will influence the feature count behind our
    7414             :         // back, so disable it in that case.
    7415             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7416             :         const bool bInsertOrDelete =
    7417        5651 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7418        2530 :             osSQLCommand.ifind("insert or replace into ") !=
    7419        8181 :                 std::string::npos ||
    7420        2493 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7421             :         const bool bRollback =
    7422        5651 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7423             : #endif
    7424             : 
    7425        7563 :         for (auto &poLayer : m_apoLayers)
    7426             :         {
    7427        1912 :             if (poLayer->SyncToDisk() != OGRERR_NONE)
    7428           0 :                 return nullptr;
    7429             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7430        2117 :             if (bRollback ||
    7431         205 :                 (bInsertOrDelete &&
    7432         205 :                  osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
    7433             :             {
    7434         203 :                 poLayer->DisableFeatureCount();
    7435             :             }
    7436             : #endif
    7437             :         }
    7438             :     }
    7439             : 
    7440        5720 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7441        5719 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7442        5719 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7443        5719 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7444             :     {
    7445           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7446             :     }
    7447        5719 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7448        5718 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7449        5718 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7450        5718 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7451             :     {
    7452           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7453             :     }
    7454             : 
    7455             :     /* -------------------------------------------------------------------- */
    7456             :     /*      DEBUG "SELECT nolock" command.                                  */
    7457             :     /* -------------------------------------------------------------------- */
    7458        5789 :     if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
    7459          69 :         EQUAL(osSQLCommand, "SELECT nolock"))
    7460             :     {
    7461           3 :         return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
    7462             :     }
    7463             : 
    7464             :     /* -------------------------------------------------------------------- */
    7465             :     /*      Special case DELLAYER: command.                                 */
    7466             :     /* -------------------------------------------------------------------- */
    7467        5717 :     if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
    7468             :     {
    7469           4 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
    7470             : 
    7471           4 :         while (*pszLayerName == ' ')
    7472           0 :             pszLayerName++;
    7473             : 
    7474           4 :         if (!DeleteVectorOrRasterLayer(pszLayerName))
    7475             :         {
    7476           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7477             :                      pszLayerName);
    7478             :         }
    7479           4 :         return nullptr;
    7480             :     }
    7481             : 
    7482             :     /* -------------------------------------------------------------------- */
    7483             :     /*      Special case RECOMPUTE EXTENT ON command.                       */
    7484             :     /* -------------------------------------------------------------------- */
    7485        5713 :     if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
    7486             :     {
    7487             :         const char *pszLayerName =
    7488           4 :             osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
    7489             : 
    7490           4 :         while (*pszLayerName == ' ')
    7491           0 :             pszLayerName++;
    7492             : 
    7493           4 :         int idx = FindLayerIndex(pszLayerName);
    7494           4 :         if (idx >= 0)
    7495             :         {
    7496           4 :             m_apoLayers[idx]->RecomputeExtent();
    7497             :         }
    7498             :         else
    7499           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7500             :                      pszLayerName);
    7501           4 :         return nullptr;
    7502             :     }
    7503             : 
    7504             :     /* -------------------------------------------------------------------- */
    7505             :     /*      Intercept DROP TABLE                                            */
    7506             :     /* -------------------------------------------------------------------- */
    7507        5709 :     if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
    7508             :     {
    7509           9 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
    7510             : 
    7511           9 :         while (*pszLayerName == ' ')
    7512           0 :             pszLayerName++;
    7513             : 
    7514           9 :         if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
    7515           4 :             return nullptr;
    7516             :     }
    7517             : 
    7518             :     /* -------------------------------------------------------------------- */
    7519             :     /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
    7520             :     /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
    7521             :     /*      and       ALTER TABLE table DROP COLUMN col_name                */
    7522             :     /*                                                                      */
    7523             :     /*      We do this because SQLite mechanisms can't deal with updating   */
    7524             :     /*      literal values in gpkg_ tables that refer to table and column   */
    7525             :     /*      names.                                                          */
    7526             :     /* -------------------------------------------------------------------- */
    7527        5705 :     if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
    7528             :     {
    7529           9 :         char **papszTokens = SQLTokenize(osSQLCommand);
    7530             :         /* ALTER TABLE src_table RENAME TO dst_table */
    7531          16 :         if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
    7532           7 :             EQUAL(papszTokens[4], "TO"))
    7533             :         {
    7534           7 :             const char *pszSrcTableName = papszTokens[2];
    7535           7 :             const char *pszDstTableName = papszTokens[5];
    7536           7 :             if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
    7537          14 :                                           SQLUnescape(pszDstTableName)))
    7538             :             {
    7539           6 :                 CSLDestroy(papszTokens);
    7540           6 :                 return nullptr;
    7541             :             }
    7542             :         }
    7543             :         /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
    7544           2 :         else if (CSLCount(papszTokens) == 8 &&
    7545           1 :                  EQUAL(papszTokens[3], "RENAME") &&
    7546           3 :                  EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
    7547             :         {
    7548           1 :             const char *pszTableName = papszTokens[2];
    7549           1 :             const char *pszSrcColumn = papszTokens[5];
    7550           1 :             const char *pszDstColumn = papszTokens[7];
    7551             :             OGRGeoPackageTableLayer *poLayer =
    7552           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7553           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7554           1 :             if (poLayer)
    7555             :             {
    7556           2 :                 int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7557           2 :                     SQLUnescape(pszSrcColumn));
    7558           1 :                 if (nSrcFieldIdx >= 0)
    7559             :                 {
    7560             :                     // OFTString or any type will do as we just alter the name
    7561             :                     // so it will be ignored.
    7562           1 :                     OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
    7563           1 :                                             OFTString);
    7564           1 :                     poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
    7565             :                                             ALTER_NAME_FLAG);
    7566           1 :                     CSLDestroy(papszTokens);
    7567           1 :                     return nullptr;
    7568             :                 }
    7569             :             }
    7570             :         }
    7571             :         /* ALTER TABLE table DROP COLUMN col_name */
    7572           2 :         else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
    7573           1 :                  EQUAL(papszTokens[4], "COLUMN"))
    7574             :         {
    7575           1 :             const char *pszTableName = papszTokens[2];
    7576           1 :             const char *pszColumnName = papszTokens[5];
    7577             :             OGRGeoPackageTableLayer *poLayer =
    7578           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7579           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7580           1 :             if (poLayer)
    7581             :             {
    7582           2 :                 int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7583           2 :                     SQLUnescape(pszColumnName));
    7584           1 :                 if (nFieldIdx >= 0)
    7585             :                 {
    7586           1 :                     poLayer->DeleteField(nFieldIdx);
    7587           1 :                     CSLDestroy(papszTokens);
    7588           1 :                     return nullptr;
    7589             :                 }
    7590             :             }
    7591             :         }
    7592           1 :         CSLDestroy(papszTokens);
    7593             :     }
    7594             : 
    7595        5697 :     if (ProcessTransactionSQL(osSQLCommand))
    7596             :     {
    7597         253 :         return nullptr;
    7598             :     }
    7599             : 
    7600        5444 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7601             :     {
    7602          13 :         ResetReadingAllLayers();
    7603             :     }
    7604        5431 :     else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
    7605             :     {
    7606             :         // Optimize truncation of a table, especially if it has a spatial
    7607             :         // index.
    7608          23 :         const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
    7609          23 :         if (aosTokens.size() == 3)
    7610             :         {
    7611          16 :             const char *pszTableName = aosTokens[2];
    7612             :             OGRGeoPackageTableLayer *poLayer =
    7613           8 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7614          24 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7615          16 :             if (poLayer)
    7616             :             {
    7617           8 :                 poLayer->Truncate();
    7618           8 :                 return nullptr;
    7619             :             }
    7620             :         }
    7621             :     }
    7622        5408 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7623           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7624        5407 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7625          67 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7626          67 :              !EQUAL(pszDialect, "DEBUG"))
    7627           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7628           1 :                                        pszDialect);
    7629             : 
    7630             :     /* -------------------------------------------------------------------- */
    7631             :     /*      Prepare statement.                                              */
    7632             :     /* -------------------------------------------------------------------- */
    7633             :     /* This will speed-up layer creation */
    7634             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7635             :     /* the layer definition. */
    7636        5434 :     bool bUseStatementForGetNextFeature = true;
    7637        5434 :     bool bEmptyLayer = false;
    7638       10868 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7639             : 
    7640       18050 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7641        6308 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7642         833 :             std::string::npos &&
    7643         833 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7644        7141 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7645         833 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7646             :     {
    7647         833 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7648         833 :         if (nOrderByPos != std::string::npos)
    7649             :         {
    7650           9 :             osSQLCommandTruncated.resize(nOrderByPos);
    7651           9 :             bUseStatementForGetNextFeature = false;
    7652             :         }
    7653             :     }
    7654             : 
    7655        5434 :     const auto nErrorCount = CPLGetErrorCounter();
    7656             :     sqlite3_stmt *hSQLStmt =
    7657        5434 :         prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7658        5434 :                    static_cast<int>(osSQLCommandTruncated.size()));
    7659        5434 :     if (!hSQLStmt)
    7660             :     {
    7661          12 :         if (nErrorCount == CPLGetErrorCounter())
    7662             :         {
    7663           9 :             CPLError(CE_Failure, CPLE_AppDefined, "%s",
    7664          18 :                      SQLFormatErrorMsgFailedPrepare(
    7665             :                          GetDB(), "In ExecuteSQL(): sqlite3_prepare_v2(): ",
    7666             :                          osSQLCommand.c_str())
    7667             :                          .c_str());
    7668             :         }
    7669          12 :         return nullptr;
    7670             :     }
    7671             : 
    7672             :     /* -------------------------------------------------------------------- */
    7673             :     /*      Do we get a resultset?                                          */
    7674             :     /* -------------------------------------------------------------------- */
    7675        5422 :     int rc = sqlite3_step(hSQLStmt);
    7676             : 
    7677        7096 :     for (auto &poLayer : m_apoLayers)
    7678             :     {
    7679        1674 :         if (!poLayer->RunDeferredDropRTreeTableIfNecessary())
    7680           0 :             return nullptr;
    7681             :     }
    7682             : 
    7683        5422 :     if (rc != SQLITE_ROW)
    7684             :     {
    7685        4635 :         if (rc != SQLITE_DONE)
    7686             :         {
    7687           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7688             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7689             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7690             : 
    7691           7 :             sqlite3_finalize(hSQLStmt);
    7692           7 :             return nullptr;
    7693             :         }
    7694             : 
    7695        4628 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7696             :         {
    7697          13 :             sqlite3_finalize(hSQLStmt);
    7698             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7699          13 :             SetApplicationAndUserVersionId();
    7700          13 :             return nullptr;
    7701             :         }
    7702             : 
    7703        4615 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7704             :         {
    7705        4487 :             sqlite3_finalize(hSQLStmt);
    7706        4487 :             return nullptr;
    7707             :         }
    7708             : 
    7709         128 :         bUseStatementForGetNextFeature = false;
    7710         128 :         bEmptyLayer = true;
    7711             :     }
    7712             : 
    7713             :     /* -------------------------------------------------------------------- */
    7714             :     /*      Special case for some functions which must be run               */
    7715             :     /*      only once                                                       */
    7716             :     /* -------------------------------------------------------------------- */
    7717         915 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7718             :     {
    7719        4199 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7720             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7721             :              i++)
    7722             :         {
    7723        3385 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7724             :                        strlen(apszFuncsWithSideEffects[i])))
    7725             :             {
    7726         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7727          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7728             :                 {
    7729          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7730             : 
    7731          56 :                     sqlite3_finalize(hSQLStmt);
    7732             : 
    7733             :                     return new OGRSQLiteSingleFeatureLayer(
    7734          56 :                         apszFuncsWithSideEffects[i], ret);
    7735             :                 }
    7736             :             }
    7737             :         }
    7738             :     }
    7739          45 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7740             :     {
    7741          63 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7742          18 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7743             :         {
    7744          15 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7745             : 
    7746          15 :             sqlite3_finalize(hSQLStmt);
    7747             : 
    7748          15 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7749          15 :                                                    ret);
    7750             :         }
    7751          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7752           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7753             :         {
    7754             :             const char *pszRet = reinterpret_cast<const char *>(
    7755           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7756             : 
    7757             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7758           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7759             : 
    7760           3 :             sqlite3_finalize(hSQLStmt);
    7761             : 
    7762           3 :             return poRet;
    7763             :         }
    7764             :     }
    7765             : 
    7766             :     /* -------------------------------------------------------------------- */
    7767             :     /*      Create layer.                                                   */
    7768             :     /* -------------------------------------------------------------------- */
    7769             : 
    7770             :     auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
    7771             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7772        1682 :         bEmptyLayer);
    7773             : 
    7774         844 :     if (poSpatialFilter != nullptr &&
    7775           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7776           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7777             : 
    7778         841 :     return poLayer.release();
    7779             : }
    7780             : 
    7781             : /************************************************************************/
    7782             : /*                          ReleaseResultSet()                          */
    7783             : /************************************************************************/
    7784             : 
    7785         874 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7786             : 
    7787             : {
    7788         874 :     delete poLayer;
    7789         874 : }
    7790             : 
    7791             : /************************************************************************/
    7792             : /*                         HasExtensionsTable()                         */
    7793             : /************************************************************************/
    7794             : 
    7795        8124 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7796             : {
    7797        8124 :     return SQLGetInteger(
    7798             :                hDB,
    7799             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7800             :                "AND type IN ('table', 'view')",
    7801        8124 :                nullptr) == 1;
    7802             : }
    7803             : 
    7804             : /************************************************************************/
    7805             : /*                       CheckUnknownExtensions()                       */
    7806             : /************************************************************************/
    7807             : 
    7808        1754 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7809             : {
    7810        1754 :     if (!HasExtensionsTable())
    7811         217 :         return;
    7812             : 
    7813        1537 :     char *pszSQL = nullptr;
    7814        1537 :     if (!bCheckRasterTable)
    7815        1320 :         pszSQL = sqlite3_mprintf(
    7816             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7817             :             "WHERE (table_name IS NULL "
    7818             :             "AND extension_name IS NOT NULL "
    7819             :             "AND definition IS NOT NULL "
    7820             :             "AND scope IS NOT NULL "
    7821             :             "AND extension_name NOT IN ("
    7822             :             "'gdal_aspatial', "
    7823             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7824             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7825             :                                        // 17-066r1 finalization
    7826             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7827             :             "'gpkg_metadata', "
    7828             :             "'gpkg_schema', "
    7829             :             "'gpkg_crs_wkt', "
    7830             :             "'gpkg_crs_wkt_1_1', "
    7831             :             "'related_tables', 'gpkg_related_tables')) "
    7832             : #ifdef WORKAROUND_SQLITE3_BUGS
    7833             :             "OR 0 "
    7834             : #endif
    7835             :             "LIMIT 1000");
    7836             :     else
    7837         217 :         pszSQL = sqlite3_mprintf(
    7838             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7839             :             "WHERE (lower(table_name) = lower('%q') "
    7840             :             "AND extension_name IS NOT NULL "
    7841             :             "AND definition IS NOT NULL "
    7842             :             "AND scope IS NOT NULL "
    7843             :             "AND extension_name NOT IN ("
    7844             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7845             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7846             :                                        // 17-066r1 finalization
    7847             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7848             :             "'gpkg_metadata', "
    7849             :             "'gpkg_schema', "
    7850             :             "'gpkg_crs_wkt', "
    7851             :             "'gpkg_crs_wkt_1_1', "
    7852             :             "'related_tables', 'gpkg_related_tables')) "
    7853             : #ifdef WORKAROUND_SQLITE3_BUGS
    7854             :             "OR 0 "
    7855             : #endif
    7856             :             "LIMIT 1000",
    7857             :             m_osRasterTable.c_str());
    7858             : 
    7859        3074 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7860        1537 :     sqlite3_free(pszSQL);
    7861        1537 :     if (oResultTable && oResultTable->RowCount() > 0)
    7862             :     {
    7863          42 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7864             :         {
    7865          21 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7866          21 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7867          21 :             const char *pszScope = oResultTable->GetValue(2, i);
    7868          21 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7869             :                 pszScope == nullptr)
    7870             :             {
    7871           0 :                 continue;
    7872             :             }
    7873             : 
    7874          21 :             if (EQUAL(pszExtName, "gpkg_webp"))
    7875             :             {
    7876          15 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    7877             :                 {
    7878           1 :                     CPLError(
    7879             :                         CE_Warning, CPLE_AppDefined,
    7880             :                         "Table %s contains WEBP tiles, but GDAL configured "
    7881             :                         "without WEBP support. Data will be missing",
    7882             :                         m_osRasterTable.c_str());
    7883             :                 }
    7884          15 :                 m_eTF = GPKG_TF_WEBP;
    7885          15 :                 continue;
    7886             :             }
    7887           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    7888             :             {
    7889           2 :                 m_bZoomOther = true;
    7890           2 :                 continue;
    7891             :             }
    7892             : 
    7893           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    7894             :             {
    7895           1 :                 CPLError(
    7896             :                     CE_Warning, CPLE_AppDefined,
    7897             :                     "Database relies on the '%s' (%s) extension that should "
    7898             :                     "be implemented for safe write-support, but is not "
    7899             :                     "currently. "
    7900             :                     "Update of that database are strongly discouraged to avoid "
    7901             :                     "corruption.",
    7902             :                     pszExtName, pszDefinition);
    7903             :             }
    7904           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    7905             :             {
    7906           1 :                 CPLError(
    7907             :                     CE_Warning, CPLE_AppDefined,
    7908             :                     "Database relies on the '%s' (%s) extension that should "
    7909             :                     "be implemented in order to read/write it safely, but is "
    7910             :                     "not currently. "
    7911             :                     "Some data may be missing while reading that database, and "
    7912             :                     "updates are strongly discouraged.",
    7913             :                     pszExtName, pszDefinition);
    7914             :             }
    7915           2 :             else if (EQUAL(pszScope, "read-write") &&
    7916             :                      // None of the NGA extensions at
    7917             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    7918             :                      // affect read-only scenarios
    7919           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    7920             :             {
    7921           1 :                 CPLError(
    7922             :                     CE_Warning, CPLE_AppDefined,
    7923             :                     "Database relies on the '%s' (%s) extension that should "
    7924             :                     "be implemented in order to read it safely, but is not "
    7925             :                     "currently. "
    7926             :                     "Some data may be missing while reading that database.",
    7927             :                     pszExtName, pszDefinition);
    7928             :             }
    7929             :         }
    7930             :     }
    7931             : }
    7932             : 
    7933             : /************************************************************************/
    7934             : /*                      HasGDALAspatialExtension()                      */
    7935             : /************************************************************************/
    7936             : 
    7937        1279 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    7938             : {
    7939        1279 :     if (!HasExtensionsTable())
    7940         103 :         return false;
    7941             : 
    7942             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    7943             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    7944             :                                       "AND table_name IS NULL "
    7945             :                                       "AND column_name IS NULL)"
    7946             : #ifdef WORKAROUND_SQLITE3_BUGS
    7947             :                                       " OR 0"
    7948             : #endif
    7949        1176 :     );
    7950        1176 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    7951        1176 :     return bHasExtension;
    7952             : }
    7953             : 
    7954             : std::string
    7955         198 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
    7956             : {
    7957             :     char *pszSQL;
    7958         198 :     std::string osSQL;
    7959             :     /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    7960             :      * Definition SQL  */
    7961         198 :     pszSQL = sqlite3_mprintf(
    7962             :         "CREATE TRIGGER \"%w_zoom_insert\" "
    7963             :         "BEFORE INSERT ON \"%w\" "
    7964             :         "FOR EACH ROW BEGIN "
    7965             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7966             :         "constraint: zoom_level not specified for table in "
    7967             :         "gpkg_tile_matrix') "
    7968             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    7969             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    7970             :         "END; "
    7971             :         "CREATE TRIGGER \"%w_zoom_update\" "
    7972             :         "BEFORE UPDATE OF zoom_level ON \"%w\" "
    7973             :         "FOR EACH ROW BEGIN "
    7974             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    7975             :         "constraint: zoom_level not specified for table in "
    7976             :         "gpkg_tile_matrix') "
    7977             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    7978             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    7979             :         "END; "
    7980             :         "CREATE TRIGGER \"%w_tile_column_insert\" "
    7981             :         "BEFORE INSERT ON \"%w\" "
    7982             :         "FOR EACH ROW BEGIN "
    7983             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7984             :         "constraint: tile_column cannot be < 0') "
    7985             :         "WHERE (NEW.tile_column < 0) ; "
    7986             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    7987             :         "constraint: tile_column must by < matrix_width specified for "
    7988             :         "table and zoom level in gpkg_tile_matrix') "
    7989             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    7990             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    7991             :         "zoom_level = NEW.zoom_level)); "
    7992             :         "END; "
    7993             :         "CREATE TRIGGER \"%w_tile_column_update\" "
    7994             :         "BEFORE UPDATE OF tile_column ON \"%w\" "
    7995             :         "FOR EACH ROW BEGIN "
    7996             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    7997             :         "constraint: tile_column cannot be < 0') "
    7998             :         "WHERE (NEW.tile_column < 0) ; "
    7999             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8000             :         "constraint: tile_column must by < matrix_width specified for "
    8001             :         "table and zoom level in gpkg_tile_matrix') "
    8002             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8003             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8004             :         "zoom_level = NEW.zoom_level)); "
    8005             :         "END; "
    8006             :         "CREATE TRIGGER \"%w_tile_row_insert\" "
    8007             :         "BEFORE INSERT ON \"%w\" "
    8008             :         "FOR EACH ROW BEGIN "
    8009             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8010             :         "constraint: tile_row cannot be < 0') "
    8011             :         "WHERE (NEW.tile_row < 0) ; "
    8012             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8013             :         "constraint: tile_row must by < matrix_height specified for "
    8014             :         "table and zoom level in gpkg_tile_matrix') "
    8015             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8016             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8017             :         "zoom_level = NEW.zoom_level)); "
    8018             :         "END; "
    8019             :         "CREATE TRIGGER \"%w_tile_row_update\" "
    8020             :         "BEFORE UPDATE OF tile_row ON \"%w\" "
    8021             :         "FOR EACH ROW BEGIN "
    8022             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8023             :         "constraint: tile_row cannot be < 0') "
    8024             :         "WHERE (NEW.tile_row < 0) ; "
    8025             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8026             :         "constraint: tile_row must by < matrix_height specified for "
    8027             :         "table and zoom level in gpkg_tile_matrix') "
    8028             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8029             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8030             :         "zoom_level = NEW.zoom_level)); "
    8031             :         "END; ",
    8032             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8033             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8034             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8035             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8036             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8037             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8038             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8039             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8040             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8041             :         osTableName.c_str());
    8042         198 :     osSQL = pszSQL;
    8043         198 :     sqlite3_free(pszSQL);
    8044         198 :     return osSQL;
    8045             : }
    8046             : 
    8047             : /************************************************************************/
    8048             : /*                  CreateExtensionsTableIfNecessary()                  */
    8049             : /************************************************************************/
    8050             : 
    8051        1488 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    8052             : {
    8053             :     /* Check if the table gpkg_extensions exists */
    8054        1488 :     if (HasExtensionsTable())
    8055         514 :         return OGRERR_NONE;
    8056             : 
    8057             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    8058             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    8059             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    8060             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    8061         974 :     const char *pszCreateGpkgExtensions =
    8062             :         "CREATE TABLE gpkg_extensions ("
    8063             :         "table_name TEXT,"
    8064             :         "column_name TEXT,"
    8065             :         "extension_name TEXT NOT NULL,"
    8066             :         "definition TEXT NOT NULL,"
    8067             :         "scope TEXT NOT NULL,"
    8068             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    8069             :         ")";
    8070             : 
    8071         974 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    8072             : }
    8073             : 
    8074             : /************************************************************************/
    8075             : /*                 OGR_GPKG_Intersects_Spatial_Filter()                 */
    8076             : /************************************************************************/
    8077             : 
    8078       23236 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    8079             :                                         sqlite3_value **argv)
    8080             : {
    8081       23236 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8082             :     {
    8083           0 :         sqlite3_result_int(pContext, 0);
    8084       23226 :         return;
    8085             :     }
    8086             : 
    8087             :     auto poLayer =
    8088       23236 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    8089             : 
    8090       23236 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8091             :     const GByte *pabyBLOB =
    8092       23236 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8093             : 
    8094             :     GPkgHeader sHeader;
    8095       46472 :     if (poLayer->m_bFilterIsEnvelope &&
    8096       23236 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    8097             :     {
    8098       23236 :         if (sHeader.bExtentHasXY)
    8099             :         {
    8100          95 :             OGREnvelope sEnvelope;
    8101          95 :             sEnvelope.MinX = sHeader.MinX;
    8102          95 :             sEnvelope.MinY = sHeader.MinY;
    8103          95 :             sEnvelope.MaxX = sHeader.MaxX;
    8104          95 :             sEnvelope.MaxY = sHeader.MaxY;
    8105          95 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    8106             :             {
    8107          31 :                 sqlite3_result_int(pContext, 1);
    8108          31 :                 return;
    8109             :             }
    8110             :         }
    8111             : 
    8112             :         // Check if at least one point falls into the layer filter envelope
    8113             :         // nHeaderLen is > 0 for GeoPackage geometries
    8114       46410 :         if (sHeader.nHeaderLen > 0 &&
    8115       23205 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8116       23205 :                                         nBLOBLen - sHeader.nHeaderLen,
    8117       23205 :                                         poLayer->m_sFilterEnvelope))
    8118             :         {
    8119       23195 :             sqlite3_result_int(pContext, 1);
    8120       23195 :             return;
    8121             :         }
    8122             :     }
    8123             : 
    8124             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8125          10 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8126          10 :     if (poGeom == nullptr)
    8127             :     {
    8128             :         // Try also spatialite geometry blobs
    8129           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8130           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8131           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8132             :         {
    8133           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8134           0 :             sqlite3_result_int(pContext, 0);
    8135           0 :             return;
    8136             :         }
    8137           0 :         poGeom.reset(poGeomSpatialite);
    8138             :     }
    8139             : 
    8140          10 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8141             : }
    8142             : 
    8143             : /************************************************************************/
    8144             : /*                        OGRGeoPackageSTMinX()                         */
    8145             : /************************************************************************/
    8146             : 
    8147      253740 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8148             :                                 sqlite3_value **argv)
    8149             : {
    8150             :     GPkgHeader sHeader;
    8151      253740 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8152             :     {
    8153          17 :         sqlite3_result_null(pContext);
    8154          17 :         return;
    8155             :     }
    8156      253723 :     sqlite3_result_double(pContext, sHeader.MinX);
    8157             : }
    8158             : 
    8159             : /************************************************************************/
    8160             : /*                        OGRGeoPackageSTMinY()                         */
    8161             : /************************************************************************/
    8162             : 
    8163      253724 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8164             :                                 sqlite3_value **argv)
    8165             : {
    8166             :     GPkgHeader sHeader;
    8167      253724 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8168             :     {
    8169           1 :         sqlite3_result_null(pContext);
    8170           1 :         return;
    8171             :     }
    8172      253723 :     sqlite3_result_double(pContext, sHeader.MinY);
    8173             : }
    8174             : 
    8175             : /************************************************************************/
    8176             : /*                        OGRGeoPackageSTMaxX()                         */
    8177             : /************************************************************************/
    8178             : 
    8179      253724 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8180             :                                 sqlite3_value **argv)
    8181             : {
    8182             :     GPkgHeader sHeader;
    8183      253724 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8184             :     {
    8185           1 :         sqlite3_result_null(pContext);
    8186           1 :         return;
    8187             :     }
    8188      253723 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8189             : }
    8190             : 
    8191             : /************************************************************************/
    8192             : /*                        OGRGeoPackageSTMaxY()                         */
    8193             : /************************************************************************/
    8194             : 
    8195      253724 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8196             :                                 sqlite3_value **argv)
    8197             : {
    8198             :     GPkgHeader sHeader;
    8199      253724 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8200             :     {
    8201           1 :         sqlite3_result_null(pContext);
    8202           1 :         return;
    8203             :     }
    8204      253723 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8205             : }
    8206             : 
    8207             : /************************************************************************/
    8208             : /*                       OGRGeoPackageSTIsEmpty()                       */
    8209             : /************************************************************************/
    8210             : 
    8211      255190 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8212             :                                    sqlite3_value **argv)
    8213             : {
    8214             :     GPkgHeader sHeader;
    8215      255190 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8216             :     {
    8217           2 :         sqlite3_result_null(pContext);
    8218           2 :         return;
    8219             :     }
    8220      255188 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8221             : }
    8222             : 
    8223             : /************************************************************************/
    8224             : /*                    OGRGeoPackageSTGeometryType()                     */
    8225             : /************************************************************************/
    8226             : 
    8227           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8228             :                                         sqlite3_value **argv)
    8229             : {
    8230             :     GPkgHeader sHeader;
    8231             : 
    8232           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8233             :     const GByte *pabyBLOB =
    8234           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8235             :     OGRwkbGeometryType eGeometryType;
    8236             : 
    8237          13 :     if (nBLOBLen < 8 ||
    8238           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8239             :     {
    8240           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8241             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8242           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8243             :         {
    8244           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8245             :                                 SQLITE_TRANSIENT);
    8246           4 :             return;
    8247             :         }
    8248             :         else
    8249             :         {
    8250           1 :             sqlite3_result_null(pContext);
    8251           1 :             return;
    8252             :         }
    8253             :     }
    8254             : 
    8255           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8256             :     {
    8257           2 :         sqlite3_result_null(pContext);
    8258           2 :         return;
    8259             :     }
    8260             : 
    8261           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8262             :                                         wkbVariantIso, &eGeometryType);
    8263           3 :     if (err != OGRERR_NONE)
    8264           1 :         sqlite3_result_null(pContext);
    8265             :     else
    8266           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8267             :                             SQLITE_TRANSIENT);
    8268             : }
    8269             : 
    8270             : /************************************************************************/
    8271             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8272             : /************************************************************************/
    8273             : 
    8274         118 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8275             :                                                int argc, sqlite3_value **argv)
    8276             : {
    8277             :     GPkgHeader sHeader;
    8278         118 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8279             :     {
    8280           2 :         sqlite3_result_int(pContext, FALSE);
    8281         107 :         return;
    8282             :     }
    8283         116 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8284         116 :     if (sHeader.MaxX < dfMinX)
    8285             :     {
    8286          93 :         sqlite3_result_int(pContext, FALSE);
    8287          93 :         return;
    8288             :     }
    8289          23 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8290          23 :     if (sHeader.MaxY < dfMinY)
    8291             :     {
    8292          11 :         sqlite3_result_int(pContext, FALSE);
    8293          11 :         return;
    8294             :     }
    8295          12 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8296          12 :     if (sHeader.MinX > dfMaxX)
    8297             :     {
    8298           1 :         sqlite3_result_int(pContext, FALSE);
    8299           1 :         return;
    8300             :     }
    8301          11 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8302          11 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8303             : }
    8304             : 
    8305             : /************************************************************************/
    8306             : /*            OGRGeoPackageSTEnvelopesIntersectsTwoParams()             */
    8307             : /************************************************************************/
    8308             : 
    8309             : static void
    8310           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8311             :                                             sqlite3_value **argv)
    8312             : {
    8313             :     GPkgHeader sHeader;
    8314           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8315             :     {
    8316           0 :         sqlite3_result_int(pContext, FALSE);
    8317           2 :         return;
    8318             :     }
    8319             :     GPkgHeader sHeader2;
    8320           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8321             :                                 1))
    8322             :     {
    8323           0 :         sqlite3_result_int(pContext, FALSE);
    8324           0 :         return;
    8325             :     }
    8326           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8327             :     {
    8328           1 :         sqlite3_result_int(pContext, FALSE);
    8329           1 :         return;
    8330             :     }
    8331           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8332             :     {
    8333           0 :         sqlite3_result_int(pContext, FALSE);
    8334           0 :         return;
    8335             :     }
    8336           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8337             :     {
    8338           1 :         sqlite3_result_int(pContext, FALSE);
    8339           1 :         return;
    8340             :     }
    8341           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8342             : }
    8343             : 
    8344             : /************************************************************************/
    8345             : /*                   OGRGeoPackageGPKGIsAssignable()                    */
    8346             : /************************************************************************/
    8347             : 
    8348           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8349             :                                           int /*argc*/, sqlite3_value **argv)
    8350             : {
    8351          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8352           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8353             :     {
    8354           2 :         sqlite3_result_int(pContext, 0);
    8355           2 :         return;
    8356             :     }
    8357             : 
    8358             :     const char *pszExpected =
    8359           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8360             :     const char *pszActual =
    8361           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8362           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8363             :                                             OGRFromOGCGeomType(pszExpected));
    8364           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8365             : }
    8366             : 
    8367             : /************************************************************************/
    8368             : /*                        OGRGeoPackageSTSRID()                         */
    8369             : /************************************************************************/
    8370             : 
    8371          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8372             :                                 sqlite3_value **argv)
    8373             : {
    8374             :     GPkgHeader sHeader;
    8375          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8376             :     {
    8377           2 :         sqlite3_result_null(pContext);
    8378           2 :         return;
    8379             :     }
    8380          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8381             : }
    8382             : 
    8383             : /************************************************************************/
    8384             : /*                        OGRGeoPackageSetSRID()                        */
    8385             : /************************************************************************/
    8386             : 
    8387          28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8388             :                                  sqlite3_value **argv)
    8389             : {
    8390          28 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8391             :     {
    8392           1 :         sqlite3_result_null(pContext);
    8393           1 :         return;
    8394             :     }
    8395          27 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8396             :     GPkgHeader sHeader;
    8397          27 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8398             :     const GByte *pabyBLOB =
    8399          27 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8400             : 
    8401          54 :     if (nBLOBLen < 8 ||
    8402          27 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8403             :     {
    8404             :         // Try also spatialite geometry blobs
    8405           0 :         OGRGeometry *poGeom = nullptr;
    8406           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8407             :             OGRERR_NONE)
    8408             :         {
    8409           0 :             sqlite3_result_null(pContext);
    8410           0 :             return;
    8411             :         }
    8412           0 :         size_t nBLOBDestLen = 0;
    8413             :         GByte *pabyDestBLOB =
    8414           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8415           0 :         if (!pabyDestBLOB)
    8416             :         {
    8417           0 :             sqlite3_result_null(pContext);
    8418           0 :             return;
    8419             :         }
    8420           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8421             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8422           0 :         return;
    8423             :     }
    8424             : 
    8425          27 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8426          27 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8427          27 :     int32_t nSRIDToSerialize = nDestSRID;
    8428          27 :     if (OGR_SWAP(sHeader.eByteOrder))
    8429           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8430          27 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8431          27 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8432             : }
    8433             : 
    8434             : /************************************************************************/
    8435             : /*                      OGRGeoPackageSTMakeValid()                      */
    8436             : /************************************************************************/
    8437             : 
    8438           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8439             :                                      sqlite3_value **argv)
    8440             : {
    8441           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8442             :     {
    8443           2 :         sqlite3_result_null(pContext);
    8444           2 :         return;
    8445             :     }
    8446           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8447             :     const GByte *pabyBLOB =
    8448           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8449             : 
    8450             :     GPkgHeader sHeader;
    8451           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8452             :     {
    8453           0 :         sqlite3_result_null(pContext);
    8454           0 :         return;
    8455             :     }
    8456             : 
    8457             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8458           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8459           1 :     if (poGeom == nullptr)
    8460             :     {
    8461             :         // Try also spatialite geometry blobs
    8462           0 :         OGRGeometry *poGeomPtr = nullptr;
    8463           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8464             :             OGRERR_NONE)
    8465             :         {
    8466           0 :             sqlite3_result_null(pContext);
    8467           0 :             return;
    8468             :         }
    8469           0 :         poGeom.reset(poGeomPtr);
    8470             :     }
    8471           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8472           1 :     if (poValid == nullptr)
    8473             :     {
    8474           0 :         sqlite3_result_null(pContext);
    8475           0 :         return;
    8476             :     }
    8477             : 
    8478           1 :     size_t nBLOBDestLen = 0;
    8479           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8480             :                                               nullptr, &nBLOBDestLen);
    8481           1 :     if (!pabyDestBLOB)
    8482             :     {
    8483           0 :         sqlite3_result_null(pContext);
    8484           0 :         return;
    8485             :     }
    8486           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8487             :                         VSIFree);
    8488             : }
    8489             : 
    8490             : /************************************************************************/
    8491             : /*                        OGRGeoPackageSTArea()                         */
    8492             : /************************************************************************/
    8493             : 
    8494          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8495             :                                 sqlite3_value **argv)
    8496             : {
    8497          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8498             :     {
    8499           1 :         sqlite3_result_null(pContext);
    8500          15 :         return;
    8501             :     }
    8502          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8503             :     const GByte *pabyBLOB =
    8504          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8505             : 
    8506             :     GPkgHeader sHeader;
    8507           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8508          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8509             :     {
    8510          16 :         if (sHeader.bEmpty)
    8511             :         {
    8512           3 :             sqlite3_result_double(pContext, 0);
    8513          13 :             return;
    8514             :         }
    8515          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8516          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8517             :         bool bNeedSwap;
    8518             :         uint32_t nType;
    8519          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8520             :         {
    8521          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8522          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8523          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8524             :             {
    8525             :                 double dfArea;
    8526           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8527             :                 {
    8528           5 :                     sqlite3_result_double(pContext, dfArea);
    8529           5 :                     return;
    8530           0 :                 }
    8531             :             }
    8532           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8533           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8534           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8535             :             {
    8536             :                 double dfArea;
    8537           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8538             :                 {
    8539           5 :                     sqlite3_result_double(pContext, dfArea);
    8540           5 :                     return;
    8541             :                 }
    8542             :             }
    8543             :         }
    8544             : 
    8545             :         // For curve geometries, fallback to OGRGeometry methods
    8546           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8547             :     }
    8548             :     else
    8549             :     {
    8550             :         // Try also spatialite geometry blobs
    8551           2 :         OGRGeometry *poGeomPtr = nullptr;
    8552           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8553             :             OGRERR_NONE)
    8554             :         {
    8555           1 :             sqlite3_result_null(pContext);
    8556           1 :             return;
    8557             :         }
    8558           1 :         poGeom.reset(poGeomPtr);
    8559             :     }
    8560           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8561           4 :     if (poSurface == nullptr)
    8562             :     {
    8563           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8564           2 :         if (poMultiSurface == nullptr)
    8565             :         {
    8566           1 :             sqlite3_result_double(pContext, 0);
    8567             :         }
    8568             :         else
    8569             :         {
    8570           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8571             :         }
    8572             :     }
    8573             :     else
    8574             :     {
    8575           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8576             :     }
    8577             : }
    8578             : 
    8579             : /************************************************************************/
    8580             : /*                     OGRGeoPackageGeodesicArea()                      */
    8581             : /************************************************************************/
    8582             : 
    8583           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8584             :                                       sqlite3_value **argv)
    8585             : {
    8586           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8587             :     {
    8588           1 :         sqlite3_result_null(pContext);
    8589           3 :         return;
    8590             :     }
    8591           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8592             :     {
    8593           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8594             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8595             :                  "use_ellipsoid = 1");
    8596             :     }
    8597             : 
    8598           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8599             :     const GByte *pabyBLOB =
    8600           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8601             :     GPkgHeader sHeader;
    8602           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8603             :     {
    8604           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8605           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8606           1 :         return;
    8607             :     }
    8608             : 
    8609             :     GDALGeoPackageDataset *poDS =
    8610           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8611             : 
    8612           3 :     auto poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8613           3 :     if (poSrcSRS == nullptr)
    8614             :     {
    8615           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8616             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8617           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8618           1 :         return;
    8619             :     }
    8620             : 
    8621             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8622           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8623           2 :     if (poGeom == nullptr)
    8624             :     {
    8625             :         // Try also spatialite geometry blobs
    8626           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8627           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8628           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8629             :         {
    8630           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8631           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8632           0 :             return;
    8633             :         }
    8634           0 :         poGeom.reset(poGeomSpatialite);
    8635             :     }
    8636             : 
    8637           2 :     poGeom->assignSpatialReference(poSrcSRS.get());
    8638           2 :     sqlite3_result_double(
    8639             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8640             : }
    8641             : 
    8642             : /************************************************************************/
    8643             : /*                OGRGeoPackageLengthOrGeodesicLength()                 */
    8644             : /************************************************************************/
    8645             : 
    8646           8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
    8647             :                                                 int argc, sqlite3_value **argv)
    8648             : {
    8649           8 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8650             :     {
    8651           2 :         sqlite3_result_null(pContext);
    8652           5 :         return;
    8653             :     }
    8654           6 :     if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
    8655             :     {
    8656           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8657             :                  "ST_Length(geom, use_ellipsoid) is only supported for "
    8658             :                  "use_ellipsoid = 1");
    8659             :     }
    8660             : 
    8661           6 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8662             :     const GByte *pabyBLOB =
    8663           6 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8664             :     GPkgHeader sHeader;
    8665           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8666             :     {
    8667           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8668           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8669           2 :         return;
    8670             :     }
    8671             : 
    8672             :     GDALGeoPackageDataset *poDS =
    8673           4 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8674             : 
    8675           4 :     OGRSpatialReferenceRefCountedPtr poSrcSRS;
    8676           4 :     if (argc == 2)
    8677             :     {
    8678           3 :         poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8679           3 :         if (!poSrcSRS)
    8680             :         {
    8681           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    8682             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8683           1 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8684           1 :             return;
    8685             :         }
    8686             :     }
    8687             : 
    8688             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8689           3 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8690           3 :     if (poGeom == nullptr)
    8691             :     {
    8692             :         // Try also spatialite geometry blobs
    8693           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8694           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8695           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8696             :         {
    8697           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8698           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8699           0 :             return;
    8700             :         }
    8701           0 :         poGeom.reset(poGeomSpatialite);
    8702             :     }
    8703             : 
    8704           3 :     if (argc == 2)
    8705           2 :         poGeom->assignSpatialReference(poSrcSRS.get());
    8706             : 
    8707           6 :     sqlite3_result_double(
    8708             :         pContext,
    8709           1 :         argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
    8710           2 :                   : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
    8711             : }
    8712             : 
    8713             : /************************************************************************/
    8714             : /*                       OGRGeoPackageTransform()                       */
    8715             : /************************************************************************/
    8716             : 
    8717             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8718             :                             sqlite3_value **argv);
    8719             : 
    8720          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8721             :                             sqlite3_value **argv)
    8722             : {
    8723          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8724          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8725             :     {
    8726           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8727          32 :         return;
    8728             :     }
    8729             : 
    8730          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8731             :     const GByte *pabyBLOB =
    8732          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8733             :     GPkgHeader sHeader;
    8734          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8735             :     {
    8736           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8737           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8738           1 :         return;
    8739             :     }
    8740             : 
    8741          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8742          29 :     if (sHeader.iSrsId == nDestSRID)
    8743             :     {
    8744             :         // Return blob unmodified
    8745           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8746           3 :         return;
    8747             :     }
    8748             : 
    8749             :     GDALGeoPackageDataset *poDS =
    8750          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8751             : 
    8752             :     // Try to get the cached coordinate transformation
    8753             :     OGRCoordinateTransformation *poCT;
    8754          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8755          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8756             :     {
    8757          20 :         poCT = poDS->m_poLastCachedCT.get();
    8758             :     }
    8759             :     else
    8760             :     {
    8761           6 :         auto poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8762           6 :         if (poSrcSRS == nullptr)
    8763             :         {
    8764           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8765             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8766           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8767           0 :             return;
    8768             :         }
    8769             : 
    8770           6 :         auto poDstSRS = poDS->GetSpatialRef(nDestSRID, true);
    8771           6 :         if (poDstSRS == nullptr)
    8772             :         {
    8773           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8774             :                      nDestSRID);
    8775           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8776           0 :             return;
    8777             :         }
    8778             :         poCT =
    8779           6 :             OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
    8780           6 :         if (poCT == nullptr)
    8781             :         {
    8782           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8783           0 :             return;
    8784             :         }
    8785             : 
    8786             :         // Cache coordinate transformation for potential later reuse
    8787           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8788           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8789           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8790           6 :         poCT = poDS->m_poLastCachedCT.get();
    8791             :     }
    8792             : 
    8793          26 :     if (sHeader.nHeaderLen >= 8)
    8794             :     {
    8795          26 :         std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
    8796          26 :         abyNewBLOB.resize(nBLOBLen);
    8797          26 :         memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
    8798             : 
    8799          26 :         OGREnvelope3D oEnv3d;
    8800          26 :         if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
    8801          26 :                              nBLOBLen - sHeader.nHeaderLen, poCT,
    8802          78 :                              poDS->m_oWKBTransformCache, oEnv3d) ||
    8803          26 :             !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
    8804             :                               oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
    8805             :                               oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
    8806             :         {
    8807           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8808           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8809           0 :             return;
    8810             :         }
    8811             : 
    8812          26 :         sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
    8813             :                             SQLITE_TRANSIENT);
    8814          26 :         return;
    8815             :     }
    8816             : 
    8817             :     // Try also spatialite geometry blobs
    8818           0 :     OGRGeometry *poGeomSpatialite = nullptr;
    8819           0 :     if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8820           0 :                                           &poGeomSpatialite) != OGRERR_NONE)
    8821             :     {
    8822           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8823           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8824           0 :         return;
    8825             :     }
    8826           0 :     auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
    8827             : 
    8828           0 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8829             :     {
    8830           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8831           0 :         return;
    8832             :     }
    8833             : 
    8834           0 :     size_t nBLOBDestLen = 0;
    8835             :     GByte *pabyDestBLOB =
    8836           0 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8837           0 :     if (!pabyDestBLOB)
    8838             :     {
    8839           0 :         sqlite3_result_null(pContext);
    8840           0 :         return;
    8841             :     }
    8842           0 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8843             :                         VSIFree);
    8844             : }
    8845             : 
    8846             : /************************************************************************/
    8847             : /*                    OGRGeoPackageSridFromAuthCRS()                    */
    8848             : /************************************************************************/
    8849             : 
    8850           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8851             :                                          int /*argc*/, sqlite3_value **argv)
    8852             : {
    8853           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8854           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8855             :     {
    8856           2 :         sqlite3_result_int(pContext, -1);
    8857           2 :         return;
    8858             :     }
    8859             : 
    8860             :     GDALGeoPackageDataset *poDS =
    8861           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8862             : 
    8863           2 :     char *pszSQL = sqlite3_mprintf(
    8864             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8865             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8866           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8867           2 :     OGRErr err = OGRERR_NONE;
    8868           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    8869           2 :     sqlite3_free(pszSQL);
    8870           2 :     if (err != OGRERR_NONE)
    8871           1 :         nSRSId = -1;
    8872           2 :     sqlite3_result_int(pContext, nSRSId);
    8873             : }
    8874             : 
    8875             : /************************************************************************/
    8876             : /*                    OGRGeoPackageImportFromEPSG()                     */
    8877             : /************************************************************************/
    8878             : 
    8879           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    8880             :                                         sqlite3_value **argv)
    8881             : {
    8882           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    8883             :     {
    8884           1 :         sqlite3_result_int(pContext, -1);
    8885           2 :         return;
    8886             :     }
    8887             : 
    8888             :     GDALGeoPackageDataset *poDS =
    8889           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8890           3 :     OGRSpatialReference oSRS;
    8891           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    8892             :     {
    8893           1 :         sqlite3_result_int(pContext, -1);
    8894           1 :         return;
    8895             :     }
    8896             : 
    8897           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    8898             : }
    8899             : 
    8900             : /************************************************************************/
    8901             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    8902             : /************************************************************************/
    8903             : 
    8904           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    8905             :                                                    int /*argc*/,
    8906             :                                                    sqlite3_value **argv)
    8907             : {
    8908           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8909           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    8910           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    8911             :     {
    8912           0 :         sqlite3_result_int(pContext, 0);
    8913           0 :         return;
    8914             :     }
    8915             : 
    8916             :     const char *pszTableName =
    8917           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8918             :     const char *pszGeomName =
    8919           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8920             :     const char *pszGeomType =
    8921           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    8922             : 
    8923             :     GDALGeoPackageDataset *poDS =
    8924           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8925             : 
    8926           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8927           1 :         poDS->GetLayerByName(pszTableName));
    8928           1 :     if (poLyr == nullptr)
    8929             :     {
    8930           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8931           0 :         sqlite3_result_int(pContext, 0);
    8932           0 :         return;
    8933             :     }
    8934           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8935             :     {
    8936           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8937           0 :         sqlite3_result_int(pContext, 0);
    8938           0 :         return;
    8939             :     }
    8940           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    8941           1 :     if (eGeomType == wkbUnknown)
    8942             :     {
    8943           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    8944           0 :         sqlite3_result_int(pContext, 0);
    8945           0 :         return;
    8946             :     }
    8947             : 
    8948           1 :     sqlite3_result_int(
    8949             :         pContext,
    8950           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    8951             : }
    8952             : 
    8953             : /************************************************************************/
    8954             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    8955             : /************************************************************************/
    8956             : 
    8957          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    8958             :                                             int /*argc*/, sqlite3_value **argv)
    8959             : {
    8960          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8961          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8962             :     {
    8963           2 :         sqlite3_result_int(pContext, 0);
    8964           2 :         return;
    8965             :     }
    8966             : 
    8967             :     const char *pszTableName =
    8968          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8969             :     const char *pszGeomName =
    8970          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8971             :     GDALGeoPackageDataset *poDS =
    8972          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8973             : 
    8974          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8975          12 :         poDS->GetLayerByName(pszTableName));
    8976          12 :     if (poLyr == nullptr)
    8977             :     {
    8978           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8979           1 :         sqlite3_result_int(pContext, 0);
    8980           1 :         return;
    8981             :     }
    8982          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8983             :     {
    8984           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8985           1 :         sqlite3_result_int(pContext, 0);
    8986           1 :         return;
    8987             :     }
    8988             : 
    8989          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    8990             : }
    8991             : 
    8992             : /************************************************************************/
    8993             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    8994             : /************************************************************************/
    8995             : 
    8996          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    8997             :                                              int /*argc*/, sqlite3_value **argv)
    8998             : {
    8999          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9000          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9001             :     {
    9002           2 :         sqlite3_result_int(pContext, 0);
    9003           2 :         return;
    9004             :     }
    9005             : 
    9006             :     const char *pszTableName =
    9007          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9008             :     const char *pszGeomName =
    9009          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9010             :     GDALGeoPackageDataset *poDS =
    9011          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9012             : 
    9013          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9014          10 :         poDS->GetLayerByName(pszTableName));
    9015          10 :     if (poLyr == nullptr)
    9016             :     {
    9017           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9018           1 :         sqlite3_result_int(pContext, 0);
    9019           1 :         return;
    9020             :     }
    9021           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9022             :     {
    9023           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9024           1 :         sqlite3_result_int(pContext, 0);
    9025           1 :         return;
    9026             :     }
    9027             : 
    9028           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    9029             : }
    9030             : 
    9031             : /************************************************************************/
    9032             : /*                    OGRGeoPackageHasSpatialIndex()                    */
    9033             : /************************************************************************/
    9034             : 
    9035          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    9036             :                                          int /*argc*/, sqlite3_value **argv)
    9037             : {
    9038          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9039          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9040             :     {
    9041           2 :         sqlite3_result_int(pContext, 0);
    9042           2 :         return;
    9043             :     }
    9044             : 
    9045             :     const char *pszTableName =
    9046          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9047             :     const char *pszGeomName =
    9048          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9049             :     GDALGeoPackageDataset *poDS =
    9050          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9051             : 
    9052          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9053          27 :         poDS->GetLayerByName(pszTableName));
    9054          27 :     if (poLyr == nullptr)
    9055             :     {
    9056           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9057           1 :         sqlite3_result_int(pContext, 0);
    9058           1 :         return;
    9059             :     }
    9060          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9061             :     {
    9062           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9063           1 :         sqlite3_result_int(pContext, 0);
    9064           1 :         return;
    9065             :     }
    9066             : 
    9067          25 :     poLyr->RunDeferredCreationIfNecessary();
    9068          25 :     poLyr->CreateSpatialIndexIfNecessary();
    9069             : 
    9070          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    9071             : }
    9072             : 
    9073             : /************************************************************************/
    9074             : /*                       GPKG_hstore_get_value()                        */
    9075             : /************************************************************************/
    9076             : 
    9077           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    9078             :                                   sqlite3_value **argv)
    9079             : {
    9080           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9081           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9082             :     {
    9083           2 :         sqlite3_result_null(pContext);
    9084           2 :         return;
    9085             :     }
    9086             : 
    9087             :     const char *pszHStore =
    9088           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9089             :     const char *pszSearchedKey =
    9090           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9091           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    9092           2 :     if (pszValue != nullptr)
    9093           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    9094             :     else
    9095           1 :         sqlite3_result_null(pContext);
    9096             : }
    9097             : 
    9098             : /************************************************************************/
    9099             : /*                    GPKG_GDAL_GetMemFileFromBlob()                    */
    9100             : /************************************************************************/
    9101             : 
    9102         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    9103             : {
    9104         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    9105             :     const GByte *pabyBLOB =
    9106         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    9107             :     CPLString osMemFileName(
    9108         105 :         VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
    9109         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    9110             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    9111         105 :     VSIFCloseL(fp);
    9112         105 :     return osMemFileName;
    9113             : }
    9114             : 
    9115             : /************************************************************************/
    9116             : /*                       GPKG_GDAL_GetMimeType()                        */
    9117             : /************************************************************************/
    9118             : 
    9119          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    9120             :                                   sqlite3_value **argv)
    9121             : {
    9122          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9123             :     {
    9124           0 :         sqlite3_result_null(pContext);
    9125           0 :         return;
    9126             :     }
    9127             : 
    9128          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9129             :     GDALDriver *poDriver =
    9130          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    9131          35 :     if (poDriver != nullptr)
    9132             :     {
    9133          35 :         const char *pszRes = nullptr;
    9134          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    9135          23 :             pszRes = "image/png";
    9136          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    9137           6 :             pszRes = "image/jpeg";
    9138           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    9139           6 :             pszRes = "image/x-webp";
    9140           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    9141           0 :             pszRes = "image/tiff";
    9142             :         else
    9143           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    9144          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    9145             :     }
    9146             :     else
    9147           0 :         sqlite3_result_null(pContext);
    9148          35 :     VSIUnlink(osMemFileName);
    9149             : }
    9150             : 
    9151             : /************************************************************************/
    9152             : /*                       GPKG_GDAL_GetBandCount()                       */
    9153             : /************************************************************************/
    9154             : 
    9155          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    9156             :                                    sqlite3_value **argv)
    9157             : {
    9158          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9159             :     {
    9160           0 :         sqlite3_result_null(pContext);
    9161           0 :         return;
    9162             :     }
    9163             : 
    9164          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9165             :     auto poDS = std::unique_ptr<GDALDataset>(
    9166             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9167          70 :                           nullptr, nullptr, nullptr));
    9168          35 :     if (poDS != nullptr)
    9169             :     {
    9170          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    9171             :     }
    9172             :     else
    9173           0 :         sqlite3_result_null(pContext);
    9174          35 :     VSIUnlink(osMemFileName);
    9175             : }
    9176             : 
    9177             : /************************************************************************/
    9178             : /*                      GPKG_GDAL_HasColorTable()                       */
    9179             : /************************************************************************/
    9180             : 
    9181          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    9182             :                                     sqlite3_value **argv)
    9183             : {
    9184          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9185             :     {
    9186           0 :         sqlite3_result_null(pContext);
    9187           0 :         return;
    9188             :     }
    9189             : 
    9190          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9191             :     auto poDS = std::unique_ptr<GDALDataset>(
    9192             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9193          70 :                           nullptr, nullptr, nullptr));
    9194          35 :     if (poDS != nullptr)
    9195             :     {
    9196          35 :         sqlite3_result_int(
    9197          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9198          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9199             :     }
    9200             :     else
    9201           0 :         sqlite3_result_null(pContext);
    9202          35 :     VSIUnlink(osMemFileName);
    9203             : }
    9204             : 
    9205             : /************************************************************************/
    9206             : /*                       GetRasterLayerDataset()                        */
    9207             : /************************************************************************/
    9208             : 
    9209             : GDALDataset *
    9210          12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9211             : {
    9212          12 :     const auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9213          12 :     if (oIter != m_oCachedRasterDS.end())
    9214          10 :         return oIter->second.get();
    9215             : 
    9216             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9217           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9218           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9219           2 :     if (!poDS)
    9220             :     {
    9221           0 :         return nullptr;
    9222             :     }
    9223           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9224           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9225             : }
    9226             : 
    9227             : /************************************************************************/
    9228             : /*                  GPKG_gdal_get_layer_pixel_value()                   */
    9229             : /************************************************************************/
    9230             : 
    9231             : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
    9232             : // and ogrgeopackagedatasource.cpp
    9233          13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
    9234             :                                             sqlite3_value **argv)
    9235             : {
    9236          13 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9237             :     {
    9238           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9239             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9240           1 :         sqlite3_result_null(pContext);
    9241           1 :         return;
    9242             :     }
    9243             : 
    9244             :     const char *pszLayerName =
    9245          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9246             : 
    9247             :     GDALGeoPackageDataset *poGlobalDS =
    9248          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9249          12 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9250          12 :     if (!poDS)
    9251             :     {
    9252           0 :         sqlite3_result_null(pContext);
    9253           0 :         return;
    9254             :     }
    9255             : 
    9256          12 :     OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
    9257             :                                           pContext, argc, argv, poDS);
    9258             : }
    9259             : 
    9260             : /************************************************************************/
    9261             : /*                       GPKG_ogr_layer_Extent()                        */
    9262             : /************************************************************************/
    9263             : 
    9264           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9265             :                                   sqlite3_value **argv)
    9266             : {
    9267           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9268             :     {
    9269           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9270             :                  "ogr_layer_Extent");
    9271           1 :         sqlite3_result_null(pContext);
    9272           2 :         return;
    9273             :     }
    9274             : 
    9275             :     const char *pszLayerName =
    9276           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9277             :     GDALGeoPackageDataset *poDS =
    9278           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9279           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9280           2 :     if (!poLayer)
    9281             :     {
    9282           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9283             :                  "ogr_layer_Extent");
    9284           1 :         sqlite3_result_null(pContext);
    9285           1 :         return;
    9286             :     }
    9287             : 
    9288           1 :     if (poLayer->GetGeomType() == wkbNone)
    9289             :     {
    9290           0 :         sqlite3_result_null(pContext);
    9291           0 :         return;
    9292             :     }
    9293             : 
    9294           1 :     OGREnvelope sExtent;
    9295           1 :     if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
    9296             :     {
    9297           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9298             :                  "ogr_layer_Extent");
    9299           0 :         sqlite3_result_null(pContext);
    9300           0 :         return;
    9301             :     }
    9302             : 
    9303           1 :     OGRPolygon oPoly;
    9304           1 :     auto poRing = std::make_unique<OGRLinearRing>();
    9305           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9306           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9307           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9308           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9309           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9310           1 :     oPoly.addRing(std::move(poRing));
    9311             : 
    9312           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9313           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9314           1 :     size_t nBLOBDestLen = 0;
    9315             :     GByte *pabyDestBLOB =
    9316           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9317           1 :     if (!pabyDestBLOB)
    9318             :     {
    9319           0 :         sqlite3_result_null(pContext);
    9320           0 :         return;
    9321             :     }
    9322           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9323             :                         VSIFree);
    9324             : }
    9325             : 
    9326             : /************************************************************************/
    9327             : /*                   GPKG_ST_Hilbert_X_Y_TableName()                    */
    9328             : /************************************************************************/
    9329             : 
    9330           8 : static void GPKG_ST_Hilbert_X_Y_TableName(sqlite3_context *pContext,
    9331             :                                           [[maybe_unused]] int argc,
    9332             :                                           sqlite3_value **argv)
    9333             : {
    9334           8 :     CPLAssert(argc == 3);
    9335           8 :     const double dfX = sqlite3_value_double(argv[0]);
    9336           8 :     const double dfY = sqlite3_value_double(argv[1]);
    9337             : 
    9338           8 :     if (sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    9339             :     {
    9340           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9341             :                  "%s: Invalid argument type for 3rd argument. Text expected",
    9342             :                  "ST_Hilbert()");
    9343           1 :         sqlite3_result_null(pContext);
    9344           6 :         return;
    9345             :     }
    9346             : 
    9347             :     const char *pszLayerName =
    9348           7 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    9349             :     GDALGeoPackageDataset *poDS =
    9350           7 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9351           7 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9352           7 :     if (!poLayer)
    9353             :     {
    9354           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
    9355             :                  "ST_Hilbert()", pszLayerName);
    9356           1 :         sqlite3_result_null(pContext);
    9357           1 :         return;
    9358             :     }
    9359             : 
    9360           6 :     OGREnvelope sExtent;
    9361           6 :     if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
    9362             :     {
    9363           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9364             :                  "ST_Hilbert()");
    9365           0 :         sqlite3_result_null(pContext);
    9366           0 :         return;
    9367             :     }
    9368           6 :     if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
    9369           3 :           dfY <= sExtent.MaxY))
    9370             :     {
    9371           4 :         CPLError(CE_Warning, CPLE_AppDefined,
    9372             :                  "ST_Hilbert(): (%g, %g) is not within passed bounding box",
    9373             :                  dfX, dfY);
    9374           4 :         sqlite3_result_null(pContext);
    9375           4 :         return;
    9376             :     }
    9377           2 :     sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
    9378             : }
    9379             : 
    9380             : /************************************************************************/
    9381             : /*                     GPKG_ST_Hilbert_Geom_BBOX()                      */
    9382             : /************************************************************************/
    9383             : 
    9384           6 : static void GPKG_ST_Hilbert_Geom_BBOX(sqlite3_context *pContext, int argc,
    9385             :                                       sqlite3_value **argv)
    9386             : {
    9387           6 :     CPLAssert(argc == 5);
    9388             :     GPkgHeader sHeader;
    9389           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    9390             :     {
    9391           1 :         sqlite3_result_null(pContext);
    9392           5 :         return;
    9393             :     }
    9394           5 :     const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
    9395           5 :     const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
    9396             : 
    9397           5 :     OGREnvelope sExtent;
    9398           5 :     sExtent.MinX = sqlite3_value_double(argv[1]);
    9399           5 :     sExtent.MinY = sqlite3_value_double(argv[2]);
    9400           5 :     sExtent.MaxX = sqlite3_value_double(argv[3]);
    9401           5 :     sExtent.MaxY = sqlite3_value_double(argv[4]);
    9402           5 :     if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
    9403           2 :           dfY <= sExtent.MaxY))
    9404             :     {
    9405           4 :         CPLError(CE_Warning, CPLE_AppDefined,
    9406             :                  "ST_Hilbert(): (%g, %g) is not within passed bounding box",
    9407             :                  dfX, dfY);
    9408           4 :         sqlite3_result_null(pContext);
    9409           4 :         return;
    9410             :     }
    9411           1 :     sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
    9412             : }
    9413             : 
    9414             : /************************************************************************/
    9415             : /*                   GPKG_ST_Hilbert_Geom_TableName()                   */
    9416             : /************************************************************************/
    9417             : 
    9418           4 : static void GPKG_ST_Hilbert_Geom_TableName(sqlite3_context *pContext, int argc,
    9419             :                                            sqlite3_value **argv)
    9420             : {
    9421           4 :     CPLAssert(argc == 2);
    9422             :     GPkgHeader sHeader;
    9423           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    9424             :     {
    9425           1 :         sqlite3_result_null(pContext);
    9426           3 :         return;
    9427             :     }
    9428           3 :     const double dfX = (sHeader.MinX + sHeader.MaxX) / 2;
    9429           3 :     const double dfY = (sHeader.MinY + sHeader.MaxY) / 2;
    9430             : 
    9431           3 :     if (sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9432             :     {
    9433           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9434             :                  "%s: Invalid argument type for 2nd argument. Text expected",
    9435             :                  "ST_Hilbert()");
    9436           1 :         sqlite3_result_null(pContext);
    9437           1 :         return;
    9438             :     }
    9439             : 
    9440             :     const char *pszLayerName =
    9441           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9442             :     GDALGeoPackageDataset *poDS =
    9443           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9444           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9445           2 :     if (!poLayer)
    9446             :     {
    9447           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer '%s'",
    9448             :                  "ST_Hilbert()", pszLayerName);
    9449           1 :         sqlite3_result_null(pContext);
    9450           1 :         return;
    9451             :     }
    9452             : 
    9453           1 :     OGREnvelope sExtent;
    9454           1 :     if (poLayer->GetExtent(&sExtent, true) != OGRERR_NONE)
    9455             :     {
    9456           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9457             :                  "ST_Hilbert()");
    9458           0 :         sqlite3_result_null(pContext);
    9459           0 :         return;
    9460             :     }
    9461           1 :     if (!(dfX >= sExtent.MinX && dfY >= sExtent.MinY && dfX <= sExtent.MaxX &&
    9462           1 :           dfY <= sExtent.MaxY))
    9463             :     {
    9464           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9465             :                  "ST_Hilbert(): (%g, %g) is not within passed bounding box",
    9466             :                  dfX, dfY);
    9467           0 :         sqlite3_result_null(pContext);
    9468           0 :         return;
    9469             :     }
    9470           1 :     sqlite3_result_int64(pContext, GDALHilbertCode(&sExtent, dfX, dfY));
    9471             : }
    9472             : 
    9473             : /************************************************************************/
    9474             : /*                        InstallSQLFunctions()                         */
    9475             : /************************************************************************/
    9476             : 
    9477             : #ifndef SQLITE_DETERMINISTIC
    9478             : #define SQLITE_DETERMINISTIC 0
    9479             : #endif
    9480             : 
    9481             : #ifndef SQLITE_INNOCUOUS
    9482             : #define SQLITE_INNOCUOUS 0
    9483             : #endif
    9484             : 
    9485             : #ifndef UTF8_INNOCUOUS
    9486             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9487             : #endif
    9488             : 
    9489        2597 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9490             : {
    9491        2597 :     InitSpatialite();
    9492             : 
    9493             :     // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
    9494             :     // that take geometries will accept and return GPKG encoded geometries without
    9495             :     // explicit conversion.
    9496             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9497             :     // error.
    9498        2597 :     sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
    9499             : 
    9500             :     /* Used by RTree Spatial Index Extension */
    9501        2597 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9502             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9503        2597 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9504             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9505        2597 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9506             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9507        2597 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9508             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9509        2597 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9510             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9511             : 
    9512             :     /* Used by Geometry Type Triggers Extension */
    9513        2597 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9514             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9515        2597 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9516             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9517             :                             nullptr);
    9518             : 
    9519             :     /* Used by Geometry SRS ID Triggers Extension */
    9520        2597 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9521             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9522             : 
    9523             :     /* Spatialite-like functions */
    9524        2597 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9525             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9526        2597 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9527             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9528        2597 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9529             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9530             : 
    9531             :     // HSTORE functions
    9532        2597 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9533             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9534             : 
    9535             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9536        2597 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9537             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9538        2597 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9539             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9540        2597 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9541             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9542             : 
    9543        2597 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9544             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9545             :                             nullptr, nullptr);
    9546        2597 :     sqlite3_create_function(
    9547             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9548             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9549             : 
    9550        2597 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9551             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9552             :                             nullptr);
    9553        2597 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9554             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9555             :                             nullptr, nullptr);
    9556             : 
    9557             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9558        2597 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9559             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9560             : 
    9561             :     // GDAL specific function
    9562        2597 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9563             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9564             : 
    9565             :     // May be used by ogrmerge.py
    9566        2597 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9567             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9568             :                             nullptr, nullptr);
    9569             : 
    9570        2597 :     if (OGRGeometryFactory::haveGEOS())
    9571             :     {
    9572        2597 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9573             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9574             :     }
    9575             : 
    9576        2597 :     sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
    9577             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9578             :                             nullptr);
    9579        2597 :     sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
    9580             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9581             :                             nullptr);
    9582             : 
    9583        2597 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9584             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9585        2597 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9586             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9587             : 
    9588             :     // Debug functions
    9589        2597 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9590             :     {
    9591         422 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9592             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9593             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9594         422 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9595             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9596             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9597         422 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9598             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9599             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9600             :     }
    9601             : 
    9602        2597 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9603             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9604             :                             nullptr);
    9605        2597 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
    9606             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9607             :                             nullptr);
    9608             : 
    9609             :     // Function from VirtualOGR
    9610        2597 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9611             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9612             : 
    9613        2597 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9614             : 
    9615             :     // ST_Hilbert() inspired from https://duckdb.org/docs/stable/core_extensions/spatial/functions#st_hilbert
    9616             :     // Override the generic version of OGRSQLiteRegisterSQLFunctionsCommon()
    9617             : 
    9618             :     // X,Y,table_name
    9619        2597 :     sqlite3_create_function(hDB, "ST_Hilbert", 2 + 1, UTF8_INNOCUOUS, this,
    9620             :                             GPKG_ST_Hilbert_X_Y_TableName, nullptr, nullptr);
    9621             : 
    9622             :     // geometry,minX,minY,maxX,maxY
    9623        2597 :     sqlite3_create_function(hDB, "ST_Hilbert", 1 + 4, UTF8_INNOCUOUS, nullptr,
    9624             :                             GPKG_ST_Hilbert_Geom_BBOX, nullptr, nullptr);
    9625             : 
    9626             :     // geometry,table_name
    9627        2597 :     sqlite3_create_function(hDB, "ST_Hilbert", 1 + 1, UTF8_INNOCUOUS, this,
    9628             :                             GPKG_ST_Hilbert_Geom_TableName, nullptr, nullptr);
    9629        2597 : }
    9630             : 
    9631             : /************************************************************************/
    9632             : /*                           OpenOrCreateDB()                           */
    9633             : /************************************************************************/
    9634             : 
    9635        2603 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9636             : {
    9637        2603 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9638             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9639             :         /*bLoadExtensions=*/true);
    9640        2603 :     if (!bSuccess)
    9641          11 :         return false;
    9642             : 
    9643             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9644             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9645             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9646        2592 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9647             : 
    9648        2592 :     InstallSQLFunctions();
    9649             : 
    9650             :     const char *pszSqlitePragma =
    9651        2592 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9652        2592 :     OGRErr eErr = OGRERR_NONE;
    9653           6 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9654             :         // Older sqlite versions don't have this pragma
    9655        5190 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9656        2592 :         eErr == OGRERR_NONE)
    9657             :     {
    9658        2592 :         bool bNeedsTrustedSchema = false;
    9659             : 
    9660             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9661             :         // able to use the RTree from triggers, which is only needed when
    9662             :         // modifying the RTree.
    9663        6369 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9664        3999 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9665        1407 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9666             :         {
    9667        1407 :             bNeedsTrustedSchema = true;
    9668             :         }
    9669             : 
    9670             : #ifdef HAVE_SPATIALITE
    9671             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9672        1185 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9673        1090 :             SQLGetInteger(
    9674             :                 hDB,
    9675             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9676             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9677           1 :                 nullptr) == 1 &&
    9678        3777 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9679             :         {
    9680           1 :             bNeedsTrustedSchema = true;
    9681             :         }
    9682             : #endif
    9683             : 
    9684        2592 :         if (bNeedsTrustedSchema)
    9685             :         {
    9686        1408 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9687        1408 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9688             :         }
    9689             :     }
    9690             : 
    9691             :     const char *pszPreludeStatements =
    9692        2592 :         CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
    9693        2592 :     if (pszPreludeStatements)
    9694             :     {
    9695           2 :         if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
    9696           0 :             return false;
    9697             :     }
    9698             : 
    9699        2592 :     return true;
    9700             : }
    9701             : 
    9702             : /************************************************************************/
    9703             : /*                 GetLayerWithGetSpatialWhereByName()                  */
    9704             : /************************************************************************/
    9705             : 
    9706             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9707          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9708             : {
    9709             :     OGRGeoPackageLayer *poRet =
    9710          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9711          90 :     return std::pair(poRet, poRet);
    9712             : }
    9713             : 
    9714             : /************************************************************************/
    9715             : /*                         CommitTransaction()                          */
    9716             : /************************************************************************/
    9717             : 
    9718         418 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9719             : 
    9720             : {
    9721         418 :     if (m_nSoftTransactionLevel == 1)
    9722             :     {
    9723         412 :         FlushMetadata();
    9724         879 :         for (auto &poLayer : m_apoLayers)
    9725             :         {
    9726         467 :             poLayer->DoJobAtTransactionCommit();
    9727             :         }
    9728             :     }
    9729             : 
    9730         418 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9731             : }
    9732             : 
    9733             : /************************************************************************/
    9734             : /*                        RollbackTransaction()                         */
    9735             : /************************************************************************/
    9736             : 
    9737          37 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9738             : 
    9739             : {
    9740             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9741          74 :     std::vector<bool> abAddTriggers;
    9742          37 :     std::vector<bool> abTriggersDeletedInTransaction;
    9743             : #endif
    9744          37 :     if (m_nSoftTransactionLevel == 1)
    9745             :     {
    9746          36 :         FlushMetadata();
    9747          74 :         for (auto &poLayer : m_apoLayers)
    9748             :         {
    9749             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9750          38 :             abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
    9751          38 :             abTriggersDeletedInTransaction.push_back(
    9752          38 :                 poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
    9753          38 :             poLayer->SetAddOGRFeatureCountTriggers(false);
    9754             : #endif
    9755          38 :             poLayer->DoJobAtTransactionRollback();
    9756             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9757          38 :             poLayer->DisableFeatureCount();
    9758             : #endif
    9759             :         }
    9760             :     }
    9761             : 
    9762          37 :     const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9763             : 
    9764             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9765          37 :     if (!abAddTriggers.empty())
    9766             :     {
    9767          72 :         for (size_t i = 0; i < m_apoLayers.size(); ++i)
    9768             :         {
    9769          38 :             auto &poLayer = m_apoLayers[i];
    9770          38 :             if (abTriggersDeletedInTransaction[i])
    9771             :             {
    9772           7 :                 poLayer->SetOGRFeatureCountTriggersEnabled(true);
    9773             :             }
    9774             :             else
    9775             :             {
    9776          31 :                 poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
    9777             :             }
    9778             :         }
    9779             :     }
    9780             : #endif
    9781          74 :     return eErr;
    9782             : }
    9783             : 
    9784             : /************************************************************************/
    9785             : /*                       GetGeometryTypeString()                        */
    9786             : /************************************************************************/
    9787             : 
    9788             : const char *
    9789        1931 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9790             : {
    9791        1931 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9792        1943 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9793          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9794             :     {
    9795           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9796             :     }
    9797        1931 :     return pszGPKGGeomType;
    9798             : }
    9799             : 
    9800             : /************************************************************************/
    9801             : /*                        GetFieldDomainNames()                         */
    9802             : /************************************************************************/
    9803             : 
    9804             : std::vector<std::string>
    9805          18 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9806             : {
    9807          18 :     if (!HasDataColumnConstraintsTable())
    9808           3 :         return std::vector<std::string>();
    9809             : 
    9810          30 :     std::vector<std::string> oDomainNamesList;
    9811             : 
    9812          15 :     std::unique_ptr<SQLResult> oResultTable;
    9813             :     {
    9814             :         std::string osSQL =
    9815             :             "SELECT DISTINCT constraint_name "
    9816             :             "FROM gpkg_data_column_constraints "
    9817             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9818             :             "ORDER BY constraint_name "
    9819          15 :             "LIMIT 10000"  // to avoid denial of service
    9820             :             ;
    9821          15 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9822          15 :         if (!oResultTable)
    9823           0 :             return oDomainNamesList;
    9824             :     }
    9825             : 
    9826          15 :     if (oResultTable->RowCount() == 10000)
    9827             :     {
    9828           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9829             :                  "Number of rows returned for field domain names has been "
    9830             :                  "truncated.");
    9831             :     }
    9832          15 :     else if (oResultTable->RowCount() > 0)
    9833             :     {
    9834          14 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9835         147 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9836             :         {
    9837         133 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9838         133 :             if (!pszConstraintName)
    9839           0 :                 continue;
    9840             : 
    9841         133 :             oDomainNamesList.emplace_back(pszConstraintName);
    9842             :         }
    9843             :     }
    9844             : 
    9845          15 :     return oDomainNamesList;
    9846             : }
    9847             : 
    9848             : /************************************************************************/
    9849             : /*                           GetFieldDomain()                           */
    9850             : /************************************************************************/
    9851             : 
    9852             : const OGRFieldDomain *
    9853         140 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9854             : {
    9855         140 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9856         140 :     if (baseRet)
    9857          43 :         return baseRet;
    9858             : 
    9859          97 :     if (!HasDataColumnConstraintsTable())
    9860           4 :         return nullptr;
    9861             : 
    9862          93 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9863          93 :     const char *min_is_inclusive =
    9864          93 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9865          93 :     const char *max_is_inclusive =
    9866          93 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9867             : 
    9868          93 :     std::unique_ptr<SQLResult> oResultTable;
    9869             :     // Note: for coded domains, we use a little trick by using a dummy
    9870             :     // _{domainname}_domain_description enum that has a single entry whose
    9871             :     // description is the description of the main domain.
    9872             :     {
    9873          93 :         char *pszSQL = sqlite3_mprintf(
    9874             :             "SELECT constraint_type, value, min, %s, "
    9875             :             "max, %s, description, constraint_name "
    9876             :             "FROM gpkg_data_column_constraints "
    9877             :             "WHERE constraint_name IN ('%q', "
    9878             :             "'_%q_domain_description') "
    9879             :             "AND length(constraint_type) < 100 "  // to
    9880             :                                                   // avoid
    9881             :                                                   // denial
    9882             :                                                   // of
    9883             :                                                   // service
    9884             :             "AND (value IS NULL OR length(value) < "
    9885             :             "10000) "  // to avoid denial
    9886             :                        // of service
    9887             :             "AND (description IS NULL OR "
    9888             :             "length(description) < 10000) "  // to
    9889             :                                              // avoid
    9890             :                                              // denial
    9891             :                                              // of
    9892             :                                              // service
    9893             :             "ORDER BY value "
    9894             :             "LIMIT 10000",  // to avoid denial of
    9895             :                             // service
    9896             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9897          93 :         oResultTable = SQLQuery(hDB, pszSQL);
    9898          93 :         sqlite3_free(pszSQL);
    9899          93 :         if (!oResultTable)
    9900           0 :             return nullptr;
    9901             :     }
    9902          93 :     if (oResultTable->RowCount() == 0)
    9903             :     {
    9904          33 :         return nullptr;
    9905             :     }
    9906          60 :     if (oResultTable->RowCount() == 10000)
    9907             :     {
    9908           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9909             :                  "Number of rows returned for field domain %s has been "
    9910             :                  "truncated.",
    9911             :                  name.c_str());
    9912             :     }
    9913             : 
    9914             :     // Try to find the field domain data type from fields that implement it
    9915          60 :     int nFieldType = -1;
    9916          60 :     OGRFieldSubType eSubType = OFSTNone;
    9917          60 :     if (HasDataColumnsTable())
    9918             :     {
    9919          55 :         char *pszSQL = sqlite3_mprintf(
    9920             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9921             :             "constraint_name = '%q' LIMIT 10",
    9922             :             name.c_str());
    9923         110 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9924          55 :         sqlite3_free(pszSQL);
    9925          55 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9926             :         {
    9927          62 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9928             :                  iRecord++)
    9929             :             {
    9930          31 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9931          31 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9932          31 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9933           0 :                     continue;
    9934             :                 OGRLayer *poLayer =
    9935          62 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9936          31 :                         pszTableName);
    9937          31 :                 if (poLayer)
    9938             :                 {
    9939          31 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9940          31 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9941          31 :                     if (nIdx >= 0)
    9942             :                     {
    9943          31 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9944          31 :                         const auto eType = poFieldDefn->GetType();
    9945          31 :                         if (nFieldType < 0)
    9946             :                         {
    9947          31 :                             nFieldType = eType;
    9948          31 :                             eSubType = poFieldDefn->GetSubType();
    9949             :                         }
    9950           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9951             :                                  nFieldType == OFTInteger)
    9952             :                         {
    9953             :                             // ok
    9954             :                         }
    9955           0 :                         else if (eType == OFTInteger &&
    9956           0 :                                  (nFieldType == OFTInteger64 ||
    9957             :                                   nFieldType == OFTReal))
    9958             :                         {
    9959           0 :                             nFieldType = OFTInteger;
    9960           0 :                             eSubType = OFSTNone;
    9961             :                         }
    9962           0 :                         else if (nFieldType != eType)
    9963             :                         {
    9964           0 :                             nFieldType = -1;
    9965           0 :                             eSubType = OFSTNone;
    9966           0 :                             break;
    9967             :                         }
    9968             :                     }
    9969             :                 }
    9970             :             }
    9971             :         }
    9972             :     }
    9973             : 
    9974          60 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9975         120 :     std::vector<OGRCodedValue> asValues;
    9976          60 :     bool error = false;
    9977         120 :     CPLString osLastConstraintType;
    9978          60 :     int nFieldTypeFromEnumCode = -1;
    9979         120 :     std::string osConstraintDescription;
    9980         120 :     std::string osDescrConstraintName("_");
    9981          60 :     osDescrConstraintName += name;
    9982          60 :     osDescrConstraintName += "_domain_description";
    9983         151 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
    9984             :     {
    9985          95 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
    9986          95 :         if (pszConstraintType == nullptr)
    9987           2 :             continue;
    9988          95 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
    9989          95 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
    9990             :         const bool bIsMinIncluded =
    9991          95 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
    9992          95 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
    9993             :         const bool bIsMaxIncluded =
    9994          95 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
    9995          95 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
    9996          95 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
    9997             : 
    9998          95 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
    9999             :         {
   10000           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10001             :                      "Only constraint of type 'enum' can have multiple rows");
   10002           1 :             error = true;
   10003           4 :             break;
   10004             :         }
   10005             : 
   10006          94 :         if (strcmp(pszConstraintType, "enum") == 0)
   10007             :         {
   10008          67 :             if (pszValue == nullptr)
   10009             :             {
   10010           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10011             :                          "NULL in 'value' column of enumeration");
   10012           1 :                 error = true;
   10013           1 :                 break;
   10014             :             }
   10015          66 :             if (osDescrConstraintName == pszConstraintName)
   10016             :             {
   10017           2 :                 if (pszDescription)
   10018             :                 {
   10019           2 :                     osConstraintDescription = pszDescription;
   10020             :                 }
   10021           2 :                 continue;
   10022             :             }
   10023          64 :             if (asValues.empty())
   10024             :             {
   10025          32 :                 asValues.reserve(oResultTable->RowCount() + 1);
   10026             :             }
   10027             :             OGRCodedValue cv;
   10028             :             // intended: the 'value' column in GPKG is actually the code
   10029          64 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
   10030          64 :             if (cv.pszCode == nullptr)
   10031             :             {
   10032           0 :                 error = true;
   10033           0 :                 break;
   10034             :             }
   10035          64 :             if (pszDescription)
   10036             :             {
   10037          50 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
   10038          50 :                 if (cv.pszValue == nullptr)
   10039             :                 {
   10040           0 :                     VSIFree(cv.pszCode);
   10041           0 :                     error = true;
   10042           0 :                     break;
   10043             :                 }
   10044             :             }
   10045             :             else
   10046             :             {
   10047          14 :                 cv.pszValue = nullptr;
   10048             :             }
   10049             : 
   10050             :             // If we can't get the data type from field definition, guess it
   10051             :             // from code.
   10052          64 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
   10053             :             {
   10054          36 :                 switch (CPLGetValueType(cv.pszCode))
   10055             :                 {
   10056          26 :                     case CPL_VALUE_INTEGER:
   10057             :                     {
   10058          26 :                         if (nFieldTypeFromEnumCode != OFTReal &&
   10059             :                             nFieldTypeFromEnumCode != OFTInteger64)
   10060             :                         {
   10061          18 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
   10062          34 :                             if (nVal < std::numeric_limits<int>::min() ||
   10063          16 :                                 nVal > std::numeric_limits<int>::max())
   10064             :                             {
   10065           6 :                                 nFieldTypeFromEnumCode = OFTInteger64;
   10066             :                             }
   10067             :                             else
   10068             :                             {
   10069          12 :                                 nFieldTypeFromEnumCode = OFTInteger;
   10070             :                             }
   10071             :                         }
   10072          26 :                         break;
   10073             :                     }
   10074             : 
   10075           6 :                     case CPL_VALUE_REAL:
   10076           6 :                         nFieldTypeFromEnumCode = OFTReal;
   10077           6 :                         break;
   10078             : 
   10079           4 :                     case CPL_VALUE_STRING:
   10080           4 :                         nFieldTypeFromEnumCode = OFTString;
   10081           4 :                         break;
   10082             :                 }
   10083             :             }
   10084             : 
   10085          64 :             asValues.emplace_back(cv);
   10086             :         }
   10087          27 :         else if (strcmp(pszConstraintType, "range") == 0)
   10088             :         {
   10089             :             OGRField sMin;
   10090             :             OGRField sMax;
   10091          20 :             OGR_RawField_SetUnset(&sMin);
   10092          20 :             OGR_RawField_SetUnset(&sMax);
   10093          20 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
   10094          11 :                 nFieldType = OFTReal;
   10095          39 :             if (pszMin != nullptr &&
   10096          19 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
   10097             :             {
   10098          15 :                 if (nFieldType == OFTInteger)
   10099           6 :                     sMin.Integer = atoi(pszMin);
   10100           9 :                 else if (nFieldType == OFTInteger64)
   10101           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
   10102             :                 else /* if( nFieldType == OFTReal ) */
   10103           6 :                     sMin.Real = CPLAtof(pszMin);
   10104             :             }
   10105          39 :             if (pszMax != nullptr &&
   10106          19 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
   10107             :             {
   10108          15 :                 if (nFieldType == OFTInteger)
   10109           6 :                     sMax.Integer = atoi(pszMax);
   10110           9 :                 else if (nFieldType == OFTInteger64)
   10111           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
   10112             :                 else /* if( nFieldType == OFTReal ) */
   10113           6 :                     sMax.Real = CPLAtof(pszMax);
   10114             :             }
   10115          20 :             poDomain = std::make_unique<OGRRangeFieldDomain>(
   10116          20 :                 name, pszDescription ? pszDescription : "",
   10117          40 :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
   10118          20 :                 bIsMinIncluded, sMax, bIsMaxIncluded);
   10119             :         }
   10120           7 :         else if (strcmp(pszConstraintType, "glob") == 0)
   10121             :         {
   10122           6 :             if (pszValue == nullptr)
   10123             :             {
   10124           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10125             :                          "NULL in 'value' column of glob");
   10126           1 :                 error = true;
   10127           1 :                 break;
   10128             :             }
   10129           5 :             if (nFieldType < 0)
   10130           1 :                 nFieldType = OFTString;
   10131           5 :             poDomain = std::make_unique<OGRGlobFieldDomain>(
   10132           5 :                 name, pszDescription ? pszDescription : "",
   10133          15 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
   10134             :         }
   10135             :         else
   10136             :         {
   10137           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10138             :                      "Unhandled constraint_type: %s", pszConstraintType);
   10139           1 :             error = true;
   10140           1 :             break;
   10141             :         }
   10142             : 
   10143          89 :         osLastConstraintType = pszConstraintType;
   10144             :     }
   10145             : 
   10146          60 :     if (!asValues.empty())
   10147             :     {
   10148          32 :         if (nFieldType < 0)
   10149          18 :             nFieldType = nFieldTypeFromEnumCode;
   10150          32 :         poDomain = std::make_unique<OGRCodedFieldDomain>(
   10151             :             name, osConstraintDescription,
   10152          64 :             static_cast<OGRFieldType>(nFieldType), eSubType,
   10153          64 :             std::move(asValues));
   10154             :     }
   10155             : 
   10156          60 :     if (error)
   10157             :     {
   10158           4 :         return nullptr;
   10159             :     }
   10160             : 
   10161          56 :     m_oMapFieldDomains[name] = std::move(poDomain);
   10162          56 :     return GDALDataset::GetFieldDomain(name);
   10163             : }
   10164             : 
   10165             : /************************************************************************/
   10166             : /*                           AddFieldDomain()                           */
   10167             : /************************************************************************/
   10168             : 
   10169          19 : bool GDALGeoPackageDataset::AddFieldDomain(
   10170             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10171             : {
   10172          38 :     const std::string domainName(domain->GetName());
   10173          19 :     if (!GetUpdate())
   10174             :     {
   10175           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10176             :                  "AddFieldDomain() not supported on read-only dataset");
   10177           0 :         return false;
   10178             :     }
   10179          19 :     if (GetFieldDomain(domainName) != nullptr)
   10180             :     {
   10181           1 :         failureReason = "A domain of identical name already exists";
   10182           1 :         return false;
   10183             :     }
   10184          18 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
   10185           0 :         return false;
   10186             : 
   10187          18 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
   10188          18 :     const char *min_is_inclusive =
   10189          18 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
   10190          18 :     const char *max_is_inclusive =
   10191          18 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
   10192             : 
   10193          18 :     const auto &osDescription = domain->GetDescription();
   10194          18 :     switch (domain->GetDomainType())
   10195             :     {
   10196          11 :         case OFDT_CODED:
   10197             :         {
   10198             :             const auto poCodedDomain =
   10199          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
   10200          11 :             if (!osDescription.empty())
   10201             :             {
   10202             :                 // We use a little trick by using a dummy
   10203             :                 // _{domainname}_domain_description enum that has a single
   10204             :                 // entry whose description is the description of the main
   10205             :                 // domain.
   10206           1 :                 char *pszSQL = sqlite3_mprintf(
   10207             :                     "INSERT INTO gpkg_data_column_constraints ("
   10208             :                     "constraint_name, constraint_type, value, "
   10209             :                     "min, %s, max, %s, "
   10210             :                     "description) VALUES ("
   10211             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
   10212             :                     "NULL, %Q)",
   10213             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10214             :                     osDescription.c_str());
   10215           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
   10216           1 :                 sqlite3_free(pszSQL);
   10217             :             }
   10218          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
   10219          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
   10220             :             {
   10221          22 :                 char *pszSQL = sqlite3_mprintf(
   10222             :                     "INSERT INTO gpkg_data_column_constraints ("
   10223             :                     "constraint_name, constraint_type, value, "
   10224             :                     "min, %s, max, %s, "
   10225             :                     "description) VALUES ("
   10226             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
   10227             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10228          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
   10229          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10230          22 :                 sqlite3_free(pszSQL);
   10231          22 :                 if (!ok)
   10232           0 :                     return false;
   10233             :             }
   10234          11 :             break;
   10235             :         }
   10236             : 
   10237           6 :         case OFDT_RANGE:
   10238             :         {
   10239             :             const auto poRangeDomain =
   10240           6 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
   10241           6 :             const auto eFieldType = poRangeDomain->GetFieldType();
   10242           6 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
   10243             :                 eFieldType != OFTReal)
   10244             :             {
   10245             :                 failureReason = "Only range domains of numeric type are "
   10246           0 :                                 "supported in GeoPackage";
   10247           0 :                 return false;
   10248             :             }
   10249             : 
   10250           6 :             double dfMin = -std::numeric_limits<double>::infinity();
   10251           6 :             double dfMax = std::numeric_limits<double>::infinity();
   10252           6 :             bool bMinIsInclusive = true;
   10253           6 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
   10254           6 :             bool bMaxIsInclusive = true;
   10255           6 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
   10256           6 :             if (eFieldType == OFTInteger)
   10257             :             {
   10258           2 :                 if (!OGR_RawField_IsUnset(&sMin))
   10259           2 :                     dfMin = sMin.Integer;
   10260           2 :                 if (!OGR_RawField_IsUnset(&sMax))
   10261           2 :                     dfMax = sMax.Integer;
   10262             :             }
   10263           4 :             else if (eFieldType == OFTInteger64)
   10264             :             {
   10265           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10266           1 :                     dfMin = static_cast<double>(sMin.Integer64);
   10267           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10268           1 :                     dfMax = static_cast<double>(sMax.Integer64);
   10269             :             }
   10270             :             else /* if( eFieldType == OFTReal ) */
   10271             :             {
   10272           3 :                 if (!OGR_RawField_IsUnset(&sMin))
   10273           3 :                     dfMin = sMin.Real;
   10274           3 :                 if (!OGR_RawField_IsUnset(&sMax))
   10275           3 :                     dfMax = sMax.Real;
   10276             :             }
   10277             : 
   10278           6 :             sqlite3_stmt *hInsertStmt = nullptr;
   10279             :             const char *pszSQL =
   10280           6 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
   10281             :                            "constraint_name, constraint_type, value, "
   10282             :                            "min, %s, max, %s, "
   10283             :                            "description) VALUES ("
   10284             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
   10285             :                            min_is_inclusive, max_is_inclusive);
   10286           6 :             if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
   10287             :                 SQLITE_OK)
   10288             :             {
   10289           0 :                 return false;
   10290             :             }
   10291           6 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
   10292           6 :                               static_cast<int>(domainName.size()),
   10293             :                               SQLITE_TRANSIENT);
   10294           6 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
   10295           6 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
   10296           6 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
   10297           6 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10298           6 :             if (osDescription.empty())
   10299             :             {
   10300           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10301             :             }
   10302             :             else
   10303             :             {
   10304           3 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10305           3 :                                   static_cast<int>(osDescription.size()),
   10306             :                                   SQLITE_TRANSIENT);
   10307             :             }
   10308           6 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10309           6 :             sqlite3_finalize(hInsertStmt);
   10310           6 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10311             :             {
   10312           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10313             :                          "failed to execute insertion '%s': %s", pszSQL,
   10314             :                          sqlite3_errmsg(hDB));
   10315           0 :                 return false;
   10316             :             }
   10317             : 
   10318           6 :             break;
   10319             :         }
   10320             : 
   10321           1 :         case OFDT_GLOB:
   10322             :         {
   10323             :             const auto poGlobDomain =
   10324           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10325           2 :             char *pszSQL = sqlite3_mprintf(
   10326             :                 "INSERT INTO gpkg_data_column_constraints ("
   10327             :                 "constraint_name, constraint_type, value, "
   10328             :                 "min, %s, max, %s, "
   10329             :                 "description) VALUES ("
   10330             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10331             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10332           1 :                 poGlobDomain->GetGlob().c_str(),
   10333           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10334           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10335           1 :             sqlite3_free(pszSQL);
   10336           1 :             if (!ok)
   10337           0 :                 return false;
   10338             : 
   10339           1 :             break;
   10340             :         }
   10341             :     }
   10342             : 
   10343          18 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10344          18 :     return true;
   10345             : }
   10346             : 
   10347             : /************************************************************************/
   10348             : /*                         UpdateFieldDomain()                          */
   10349             : /************************************************************************/
   10350             : 
   10351           3 : bool GDALGeoPackageDataset::UpdateFieldDomain(
   10352             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10353             : {
   10354           6 :     const std::string domainName(domain->GetName());
   10355           3 :     if (eAccess != GA_Update)
   10356             :     {
   10357           1 :         CPLError(CE_Failure, CPLE_NotSupported,
   10358             :                  "UpdateFieldDomain() not supported on read-only dataset");
   10359           1 :         return false;
   10360             :     }
   10361             : 
   10362           2 :     if (GetFieldDomain(domainName) == nullptr)
   10363             :     {
   10364           1 :         failureReason = "The domain should already exist to be updated";
   10365           1 :         return false;
   10366             :     }
   10367             : 
   10368           1 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
   10369           1 :     if (bRet)
   10370             :     {
   10371           2 :         bRet = DeleteFieldDomain(domainName, failureReason) &&
   10372           1 :                AddFieldDomain(std::move(domain), failureReason);
   10373           1 :         if (bRet)
   10374           1 :             bRet = SoftCommitTransaction() == OGRERR_NONE;
   10375             :         else
   10376           0 :             SoftRollbackTransaction();
   10377             :     }
   10378           1 :     return bRet;
   10379             : }
   10380             : 
   10381             : /************************************************************************/
   10382             : /*                         DeleteFieldDomain()                          */
   10383             : /************************************************************************/
   10384             : 
   10385          18 : bool GDALGeoPackageDataset::DeleteFieldDomain(const std::string &name,
   10386             :                                               std::string &failureReason)
   10387             : {
   10388          18 :     if (eAccess != GA_Update)
   10389             :     {
   10390           1 :         CPLError(CE_Failure, CPLE_NotSupported,
   10391             :                  "DeleteFieldDomain() not supported on read-only dataset");
   10392           1 :         return false;
   10393             :     }
   10394          17 :     if (GetFieldDomain(name) == nullptr)
   10395             :     {
   10396           1 :         failureReason = "Domain does not exist";
   10397           1 :         return false;
   10398             :     }
   10399             : 
   10400             :     char *pszSQL =
   10401          16 :         sqlite3_mprintf("DELETE FROM gpkg_data_column_constraints WHERE "
   10402             :                         "constraint_name IN ('%q', '_%q_domain_description')",
   10403             :                         name.c_str(), name.c_str());
   10404          16 :     const bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10405          16 :     sqlite3_free(pszSQL);
   10406          16 :     if (ok)
   10407          16 :         m_oMapFieldDomains.erase(name);
   10408          16 :     return ok;
   10409             : }
   10410             : 
   10411             : /************************************************************************/
   10412             : /*                          AddRelationship()                           */
   10413             : /************************************************************************/
   10414             : 
   10415          26 : bool GDALGeoPackageDataset::AddRelationship(
   10416             :     std::unique_ptr<GDALRelationship> &&relationship,
   10417             :     std::string &failureReason)
   10418             : {
   10419          26 :     if (!GetUpdate())
   10420             :     {
   10421           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10422             :                  "AddRelationship() not supported on read-only dataset");
   10423           0 :         return false;
   10424             :     }
   10425             : 
   10426             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10427          26 :         relationship->GetLeftTableName().c_str(),
   10428          26 :         relationship->GetRightTableName().c_str(),
   10429         104 :         relationship->GetRelatedTableType().c_str());
   10430             :     // sanity checks
   10431          26 :     if (GetRelationship(osRelationshipName) != nullptr)
   10432             :     {
   10433           1 :         failureReason = "A relationship of identical name already exists";
   10434           1 :         return false;
   10435             :     }
   10436             : 
   10437          25 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10438             :     {
   10439          14 :         return false;
   10440             :     }
   10441             : 
   10442          75 :     for (auto &poLayer : m_apoLayers)
   10443             :     {
   10444          64 :         if (poLayer->SyncToDisk() != OGRERR_NONE)
   10445           0 :             return false;
   10446             :     }
   10447             : 
   10448          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10449             :     {
   10450           0 :         return false;
   10451             :     }
   10452          11 :     if (!CreateRelationsTableIfNecessary())
   10453             :     {
   10454           0 :         failureReason = "Could not create gpkgext_relations table";
   10455           0 :         return false;
   10456             :     }
   10457          11 :     if (SQLGetInteger(GetDB(),
   10458             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10459             :                       "table_name = 'gpkgext_relations'",
   10460          11 :                       nullptr) != 1)
   10461             :     {
   10462           5 :         if (OGRERR_NONE !=
   10463           5 :             SQLCommand(
   10464             :                 GetDB(),
   10465             :                 "INSERT INTO gpkg_extensions "
   10466             :                 "(table_name,column_name,extension_name,definition,scope) "
   10467             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10468             :                 "'http://www.geopackage.org/18-000.html', "
   10469             :                 "'read-write')"))
   10470             :         {
   10471             :             failureReason =
   10472           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10473           0 :             return false;
   10474             :         }
   10475             :     }
   10476             : 
   10477          11 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10478          11 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10479          11 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10480          11 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10481             : 
   10482          22 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10483          11 :     if (osRelatedTableType.empty())
   10484             :     {
   10485           5 :         osRelatedTableType = "features";
   10486             :     }
   10487             : 
   10488             :     // generate mapping table if not set
   10489          22 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10490          11 :     if (osMappingTableName.empty())
   10491             :     {
   10492           5 :         int nIndex = 1;
   10493           5 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10494           5 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10495             :         {
   10496           0 :             nIndex += 1;
   10497             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10498           0 :                                       osRightTableName.c_str(), nIndex);
   10499             :         }
   10500             : 
   10501             :         // determine whether base/related keys are unique
   10502           5 :         bool bBaseKeyIsUnique = false;
   10503             :         {
   10504             :             const std::set<std::string> uniqueBaseFieldsUC =
   10505             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10506          10 :                                                osLeftTableName.c_str());
   10507          10 :             if (uniqueBaseFieldsUC.find(
   10508           5 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10509          10 :                 uniqueBaseFieldsUC.end())
   10510             :             {
   10511           2 :                 bBaseKeyIsUnique = true;
   10512             :             }
   10513             :         }
   10514           5 :         bool bRelatedKeyIsUnique = false;
   10515             :         {
   10516             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10517             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10518          10 :                                                osRightTableName.c_str());
   10519          10 :             if (uniqueRelatedFieldsUC.find(
   10520           5 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10521          10 :                 uniqueRelatedFieldsUC.end())
   10522             :             {
   10523           2 :                 bRelatedKeyIsUnique = true;
   10524             :             }
   10525             :         }
   10526             : 
   10527             :         // create mapping table
   10528             : 
   10529           5 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10530           5 :         if (bBaseKeyIsUnique)
   10531             :         {
   10532           2 :             char *pszSQL = sqlite3_mprintf(
   10533             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10534             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10535             :                 "DEFERRED",
   10536             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10537           2 :                 aosLeftTableFields[0].c_str());
   10538           2 :             osBaseIdDefinition += pszSQL;
   10539           2 :             sqlite3_free(pszSQL);
   10540             :         }
   10541             : 
   10542           5 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10543           5 :         if (bRelatedKeyIsUnique)
   10544             :         {
   10545           2 :             char *pszSQL = sqlite3_mprintf(
   10546             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10547             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10548             :                 "DEFERRED",
   10549             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10550           2 :                 aosRightTableFields[0].c_str());
   10551           2 :             osRelatedIdDefinition += pszSQL;
   10552           2 :             sqlite3_free(pszSQL);
   10553             :         }
   10554             : 
   10555           5 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10556             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10557             :                                        "%s, %s);",
   10558             :                                        osMappingTableName.c_str(),
   10559             :                                        osBaseIdDefinition.c_str(),
   10560             :                                        osRelatedIdDefinition.c_str());
   10561           5 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10562           5 :         sqlite3_free(pszSQL);
   10563           5 :         if (eErr != OGRERR_NONE)
   10564             :         {
   10565             :             failureReason =
   10566           0 :                 ("Could not create mapping table " + osMappingTableName)
   10567           0 :                     .c_str();
   10568           0 :             return false;
   10569             :         }
   10570             : 
   10571             :         /*
   10572             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10573             :          * The related tables extension explicitly states that the mapping table should only be
   10574             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10575             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10576             :          *
   10577             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10578             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10579             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10580             :          *
   10581             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10582             :          */
   10583           5 :         pszSQL = sqlite3_mprintf(
   10584             :             "INSERT INTO gpkg_contents "
   10585             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10586             :             "VALUES "
   10587             :             "('%q','attributes','%q','Mapping table for relationship between "
   10588             :             "%q and %q',%s,0)",
   10589             :             osMappingTableName.c_str(), /*table_name*/
   10590             :             osMappingTableName.c_str(), /*identifier*/
   10591             :             osLeftTableName.c_str(),    /*description left table name*/
   10592             :             osRightTableName.c_str(),   /*description right table name*/
   10593          10 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10594             : 
   10595             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10596             :         // supposed to be adding this table to gpkg_contents anyway!
   10597           5 :         (void)SQLCommand(hDB, pszSQL);
   10598           5 :         sqlite3_free(pszSQL);
   10599             : 
   10600           5 :         pszSQL = sqlite3_mprintf(
   10601             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10602             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10603           5 :         eErr = SQLCommand(hDB, pszSQL);
   10604           5 :         sqlite3_free(pszSQL);
   10605           5 :         if (eErr != OGRERR_NONE)
   10606             :         {
   10607           0 :             failureReason = ("Could not create index for " +
   10608           0 :                              osMappingTableName + " (base_id)")
   10609           0 :                                 .c_str();
   10610           0 :             return false;
   10611             :         }
   10612             : 
   10613           5 :         pszSQL = sqlite3_mprintf(
   10614             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10615             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10616           5 :         eErr = SQLCommand(hDB, pszSQL);
   10617           5 :         sqlite3_free(pszSQL);
   10618           5 :         if (eErr != OGRERR_NONE)
   10619             :         {
   10620           0 :             failureReason = ("Could not create index for " +
   10621           0 :                              osMappingTableName + " (related_id)")
   10622           0 :                                 .c_str();
   10623           0 :             return false;
   10624             :         }
   10625             : 
   10626             :         auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
   10627          10 :             this, osMappingTableName.c_str());
   10628           5 :         poLayer->SetOpeningParameters(osMappingTableName.c_str(), "table",
   10629             :                                       /* bIsInGpkgContents = */ true,
   10630             :                                       /* bIsSpatial = */ false,
   10631             :                                       /* pszGeomColName =*/nullptr,
   10632             :                                       /* pszGeomType =*/nullptr,
   10633             :                                       /* bHasZ = */ false, /* bHasM = */ false);
   10634           5 :         m_apoLayers.push_back(std::move(poLayer));
   10635             :     }
   10636             :     else
   10637             :     {
   10638             :         // validate mapping table structure
   10639           6 :         if (OGRGeoPackageTableLayer *poLayer =
   10640           6 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10641           6 :                     GetLayerByName(osMappingTableName)))
   10642             :         {
   10643           4 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10644             :             {
   10645             :                 failureReason =
   10646           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10647           1 :                         .c_str();
   10648           1 :                 return false;
   10649             :             }
   10650           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10651             :             {
   10652             :                 failureReason =
   10653           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10654           1 :                         .c_str();
   10655           1 :                 return false;
   10656             :             }
   10657             :         }
   10658             :         else
   10659             :         {
   10660             :             failureReason =
   10661           2 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10662           2 :             return false;
   10663             :         }
   10664             :     }
   10665             : 
   10666           7 :     char *pszSQL = sqlite3_mprintf(
   10667             :         "INSERT INTO gpkg_extensions "
   10668             :         "(table_name,column_name,extension_name,definition,scope) "
   10669             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10670             :         "'http://www.geopackage.org/18-000.html', "
   10671             :         "'read-write')",
   10672             :         osMappingTableName.c_str());
   10673           7 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10674           7 :     sqlite3_free(pszSQL);
   10675           7 :     if (eErr != OGRERR_NONE)
   10676             :     {
   10677           0 :         failureReason = ("Could not insert mapping table " +
   10678           0 :                          osMappingTableName + " into gpkg_extensions")
   10679           0 :                             .c_str();
   10680           0 :         return false;
   10681             :     }
   10682             : 
   10683          21 :     pszSQL = sqlite3_mprintf(
   10684             :         "INSERT INTO gpkgext_relations "
   10685             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10686             :         "primary_column,relation_name,mapping_table_name) "
   10687             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10688           7 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10689           7 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10690             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10691           7 :     eErr = SQLCommand(hDB, pszSQL);
   10692           7 :     sqlite3_free(pszSQL);
   10693           7 :     if (eErr != OGRERR_NONE)
   10694             :     {
   10695           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10696           0 :         return false;
   10697             :     }
   10698             : 
   10699           7 :     ClearCachedRelationships();
   10700           7 :     LoadRelationships();
   10701           7 :     return true;
   10702             : }
   10703             : 
   10704             : /************************************************************************/
   10705             : /*                         DeleteRelationship()                         */
   10706             : /************************************************************************/
   10707             : 
   10708           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10709             :                                                std::string &failureReason)
   10710             : {
   10711           4 :     if (eAccess != GA_Update)
   10712             :     {
   10713           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10714             :                  "DeleteRelationship() not supported on read-only dataset");
   10715           0 :         return false;
   10716             :     }
   10717             : 
   10718             :     // ensure relationships are up to date before we try to remove one
   10719           4 :     ClearCachedRelationships();
   10720           4 :     LoadRelationships();
   10721             : 
   10722           8 :     std::string osMappingTableName;
   10723             :     {
   10724           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10725           4 :         if (poRelationship == nullptr)
   10726             :         {
   10727           1 :             failureReason = "Could not find relationship with name " + name;
   10728           1 :             return false;
   10729             :         }
   10730             : 
   10731           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10732             :     }
   10733             : 
   10734             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10735             :     // refer to poRelationship or any of its members previously obtained here
   10736           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10737             :     {
   10738             :         failureReason =
   10739           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10740             : 
   10741             :         // relationships may have been left in an inconsistent state -- reload
   10742             :         // them now
   10743           0 :         ClearCachedRelationships();
   10744           0 :         LoadRelationships();
   10745           0 :         return false;
   10746             :     }
   10747             : 
   10748           3 :     ClearCachedRelationships();
   10749           3 :     LoadRelationships();
   10750           3 :     return true;
   10751             : }
   10752             : 
   10753             : /************************************************************************/
   10754             : /*                         UpdateRelationship()                         */
   10755             : /************************************************************************/
   10756             : 
   10757           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10758             :     std::unique_ptr<GDALRelationship> &&relationship,
   10759             :     std::string &failureReason)
   10760             : {
   10761           6 :     if (eAccess != GA_Update)
   10762             :     {
   10763           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10764             :                  "UpdateRelationship() not supported on read-only dataset");
   10765           0 :         return false;
   10766             :     }
   10767             : 
   10768             :     // ensure relationships are up to date before we try to update one
   10769           6 :     ClearCachedRelationships();
   10770           6 :     LoadRelationships();
   10771             : 
   10772           6 :     const std::string &osRelationshipName = relationship->GetName();
   10773           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10774           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10775           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10776           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10777           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10778             : 
   10779             :     // sanity checks
   10780             :     {
   10781             :         const GDALRelationship *poExistingRelationship =
   10782           6 :             GetRelationship(osRelationshipName);
   10783           6 :         if (poExistingRelationship == nullptr)
   10784             :         {
   10785             :             failureReason =
   10786           1 :                 "The relationship should already exist to be updated";
   10787           1 :             return false;
   10788             :         }
   10789             : 
   10790           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10791             :         {
   10792           2 :             return false;
   10793             :         }
   10794             : 
   10795             :         // we don't permit changes to the participating tables
   10796           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10797             :         {
   10798           0 :             failureReason = ("Cannot change base table from " +
   10799           0 :                              poExistingRelationship->GetLeftTableName() +
   10800           0 :                              " to " + osLeftTableName)
   10801           0 :                                 .c_str();
   10802           0 :             return false;
   10803             :         }
   10804           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10805             :         {
   10806           0 :             failureReason = ("Cannot change related table from " +
   10807           0 :                              poExistingRelationship->GetRightTableName() +
   10808           0 :                              " to " + osRightTableName)
   10809           0 :                                 .c_str();
   10810           0 :             return false;
   10811             :         }
   10812           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10813             :         {
   10814           0 :             failureReason = ("Cannot change mapping table from " +
   10815           0 :                              poExistingRelationship->GetMappingTableName() +
   10816           0 :                              " to " + osMappingTableName)
   10817           0 :                                 .c_str();
   10818           0 :             return false;
   10819             :         }
   10820             :     }
   10821             : 
   10822           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10823           3 :     if (osRelatedTableType.empty())
   10824             :     {
   10825           0 :         osRelatedTableType = "features";
   10826             :     }
   10827             : 
   10828           3 :     char *pszSQL = sqlite3_mprintf(
   10829             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10830             :         osMappingTableName.c_str());
   10831           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10832           3 :     sqlite3_free(pszSQL);
   10833           3 :     if (eErr != OGRERR_NONE)
   10834             :     {
   10835             :         failureReason =
   10836           0 :             "Could not delete old relationship from gpkgext_relations";
   10837           0 :         return false;
   10838             :     }
   10839             : 
   10840           9 :     pszSQL = sqlite3_mprintf(
   10841             :         "INSERT INTO gpkgext_relations "
   10842             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10843             :         "primary_column,relation_name,mapping_table_name) "
   10844             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10845           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10846           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10847             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10848           3 :     eErr = SQLCommand(hDB, pszSQL);
   10849           3 :     sqlite3_free(pszSQL);
   10850           3 :     if (eErr != OGRERR_NONE)
   10851             :     {
   10852             :         failureReason =
   10853           0 :             "Could not insert updated relationship into gpkgext_relations";
   10854           0 :         return false;
   10855             :     }
   10856             : 
   10857           3 :     ClearCachedRelationships();
   10858           3 :     LoadRelationships();
   10859           3 :     return true;
   10860             : }
   10861             : 
   10862             : /************************************************************************/
   10863             : /*                       GetSqliteMasterContent()                       */
   10864             : /************************************************************************/
   10865             : 
   10866             : const std::vector<SQLSqliteMasterContent> &
   10867           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10868             : {
   10869           2 :     if (m_aoSqliteMasterContent.empty())
   10870             :     {
   10871             :         auto oResultTable =
   10872           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10873           1 :         if (oResultTable)
   10874             :         {
   10875          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10876             :             {
   10877         114 :                 SQLSqliteMasterContent row;
   10878          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10879          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10880          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10881          57 :                 row.osType = pszType ? pszType : "";
   10882          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10883          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10884          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10885             :             }
   10886             :         }
   10887             :     }
   10888           2 :     return m_aoSqliteMasterContent;
   10889             : }

Generated by: LCOV version 1.14