LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4086 4574 89.3 %
Date: 2024-11-21 22:18:42 Functions: 136 136 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GeoPackage Translator
       4             :  * Purpose:  Implements GDALGeoPackageDataset class
       5             :  * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
       9             :  * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "ogr_geopackage.h"
      15             : #include "ogr_p.h"
      16             : #include "ogr_swq.h"
      17             : #include "gdalwarper.h"
      18             : #include "gdal_utils.h"
      19             : #include "ogrgeopackageutility.h"
      20             : #include "ogrsqliteutility.h"
      21             : #include "ogr_wkb.h"
      22             : #include "vrt/vrtdataset.h"
      23             : 
      24             : #include "tilematrixset.hpp"
      25             : 
      26             : #include <cstdlib>
      27             : 
      28             : #include <algorithm>
      29             : #include <limits>
      30             : #include <sstream>
      31             : 
      32             : #define COMPILATION_ALLOWED
      33             : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
      34             : #include "ogrsqlitesqlfunctionscommon.cpp"
      35             : 
      36             : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
      37             : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
      38             : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
      39             :                                       const GByte *pabyHeader,
      40             :                                       int nHeaderBytes);
      41             : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
      42             : 
      43             : /************************************************************************/
      44             : /*                             Tiling schemes                           */
      45             : /************************************************************************/
      46             : 
      47             : typedef struct
      48             : {
      49             :     const char *pszName;
      50             :     int nEPSGCode;
      51             :     double dfMinX;
      52             :     double dfMaxY;
      53             :     int nTileXCountZoomLevel0;
      54             :     int nTileYCountZoomLevel0;
      55             :     int nTileWidth;
      56             :     int nTileHeight;
      57             :     double dfPixelXSizeZoomLevel0;
      58             :     double dfPixelYSizeZoomLevel0;
      59             : } TilingSchemeDefinition;
      60             : 
      61             : static const TilingSchemeDefinition asTilingSchemes[] = {
      62             :     /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
      63             :        Annex E.3 */
      64             :     {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
      65             :      360.0 / 256},
      66             : 
      67             :     /* See global-mercator at
      68             :        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
      69             :     {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
      70             :      256, 78271.516, 78271.516},
      71             : };
      72             : 
      73             : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
      74             : constexpr int MAX_ZOOM_LEVEL = 30;
      75             : 
      76             : /************************************************************************/
      77             : /*                     GetTilingScheme()                                */
      78             : /************************************************************************/
      79             : 
      80             : static std::unique_ptr<TilingSchemeDefinition>
      81         508 : GetTilingScheme(const char *pszName)
      82             : {
      83         508 :     if (EQUAL(pszName, "CUSTOM"))
      84         380 :         return nullptr;
      85             : 
      86         256 :     for (const auto &tilingScheme : asTilingSchemes)
      87             :     {
      88         195 :         if (EQUAL(pszName, tilingScheme.pszName))
      89             :         {
      90             :             return std::unique_ptr<TilingSchemeDefinition>(
      91          67 :                 new TilingSchemeDefinition(tilingScheme));
      92             :         }
      93             :     }
      94             : 
      95          61 :     if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
      96           6 :         pszName = "InspireCRS84Quad";
      97             : 
      98         122 :     auto poTM = gdal::TileMatrixSet::parse(pszName);
      99          61 :     if (poTM == nullptr)
     100           1 :         return nullptr;
     101          60 :     if (!poTM->haveAllLevelsSameTopLeft())
     102             :     {
     103           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     104             :                  "Unsupported tiling scheme: not all zoom levels have same top "
     105             :                  "left corner");
     106           0 :         return nullptr;
     107             :     }
     108          60 :     if (!poTM->haveAllLevelsSameTileSize())
     109             :     {
     110           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     111             :                  "Unsupported tiling scheme: not all zoom levels have same "
     112             :                  "tile size");
     113           0 :         return nullptr;
     114             :     }
     115          60 :     if (!poTM->hasOnlyPowerOfTwoVaryingScales())
     116             :     {
     117           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     118             :                  "Unsupported tiling scheme: resolution of consecutive zoom "
     119             :                  "levels is not always 2");
     120           1 :         return nullptr;
     121             :     }
     122          59 :     if (poTM->hasVariableMatrixWidth())
     123             :     {
     124           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     125             :                  "Unsupported tiling scheme: some levels have variable matrix "
     126             :                  "width");
     127           0 :         return nullptr;
     128             :     }
     129         118 :     auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
     130          59 :     poTilingScheme->pszName = pszName;
     131             : 
     132         118 :     OGRSpatialReference oSRS;
     133          59 :     if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
     134             :     {
     135           0 :         return nullptr;
     136             :     }
     137          59 :     if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
     138             :     {
     139           6 :         poTilingScheme->nEPSGCode = 4326;
     140             :     }
     141             :     else
     142             :     {
     143          53 :         const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
     144          53 :         const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
     145          53 :         if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
     146             :             pszAuthCode == nullptr)
     147             :         {
     148           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     149             :                      "Unsupported tiling scheme: only EPSG CRS supported");
     150           0 :             return nullptr;
     151             :         }
     152          53 :         poTilingScheme->nEPSGCode = atoi(pszAuthCode);
     153             :     }
     154          59 :     const auto &zoomLevel0 = poTM->tileMatrixList()[0];
     155          59 :     poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
     156          59 :     poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
     157          59 :     poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
     158          59 :     poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
     159          59 :     poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
     160          59 :     poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
     161          59 :     poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
     162          59 :     poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
     163             : 
     164         118 :     const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
     165          59 :                              oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
     166          59 :     if (bInvertAxis)
     167             :     {
     168           6 :         std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
     169           6 :         std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
     170           6 :                   poTilingScheme->dfPixelYSizeZoomLevel0);
     171             :     }
     172          59 :     return poTilingScheme;
     173             : }
     174             : 
     175             : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
     176             :     "CREATE TABLE gpkg_geometry_columns ("
     177             :     "table_name TEXT NOT NULL,"
     178             :     "column_name TEXT NOT NULL,"
     179             :     "geometry_type_name TEXT NOT NULL,"
     180             :     "srs_id INTEGER NOT NULL,"
     181             :     "z TINYINT NOT NULL,"
     182             :     "m TINYINT NOT NULL,"
     183             :     "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
     184             :     "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
     185             :     "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
     186             :     "gpkg_contents(table_name),"
     187             :     "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
     188             :     "(srs_id)"
     189             :     ")";
     190             : 
     191             : /* Only recent versions of SQLite will let us muck with application_id */
     192             : /* via a PRAGMA statement, so we have to write directly into the */
     193             : /* file header here. */
     194             : /* We do this at the *end* of initialization so that there is */
     195             : /* data to write down to a file, and we will have a writable file */
     196             : /* once we close the SQLite connection */
     197         751 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     198             : {
     199         751 :     CPLAssert(hDB != nullptr);
     200             : 
     201         751 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     202             :                                                 "PRAGMA user_version = %u",
     203             :                                                 m_nApplicationId,
     204        1502 :                                                 m_nUserVersion));
     205        1502 :     return SQLCommand(hDB, osPragma.c_str());
     206             : }
     207             : 
     208        2182 : bool GDALGeoPackageDataset::CloseDB()
     209             : {
     210        2182 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     211        2182 :     m_pSQLFunctionData = nullptr;
     212        2182 :     return OGRSQLiteBaseDataSource::CloseDB();
     213             : }
     214             : 
     215          11 : bool GDALGeoPackageDataset::ReOpenDB()
     216             : {
     217          11 :     CPLAssert(hDB != nullptr);
     218          11 :     CPLAssert(m_pszFilename != nullptr);
     219             : 
     220          11 :     FinishSpatialite();
     221             : 
     222          11 :     CloseDB();
     223             : 
     224             :     /* And re-open the file */
     225          11 :     return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
     226             : }
     227             : 
     228         700 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     229             :                                      int nEPSGCode)
     230             : {
     231         700 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     232         700 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     233         700 :     CPLPopErrorHandler();
     234         700 :     CPLErrorReset();
     235         700 :     return eErr;
     236             : }
     237             : 
     238             : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
     239        1074 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     240             :                                      bool bEmitErrorIfNotFound)
     241             : {
     242        1074 :     const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
     243        1074 :     if (oIter != m_oMapSrsIdToSrs.end())
     244             :     {
     245          65 :         if (oIter->second == nullptr)
     246          29 :             return nullptr;
     247          36 :         oIter->second->Reference();
     248             :         return std::unique_ptr<OGRSpatialReference,
     249          36 :                                OGRSpatialReferenceReleaser>(oIter->second);
     250             :     }
     251             : 
     252        1009 :     if (iSrsId == 0 || iSrsId == -1)
     253             :     {
     254         119 :         OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     255         119 :         poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     256             : 
     257             :         // See corresponding tests in GDALGeoPackageDataset::GetSrsId
     258         119 :         if (iSrsId == 0)
     259             :         {
     260          29 :             poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
     261             :                                     "unknown", SRS_WGS84_SEMIMAJOR,
     262             :                                     SRS_WGS84_INVFLATTENING);
     263             :         }
     264          90 :         else if (iSrsId == -1)
     265             :         {
     266          90 :             poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
     267          90 :             poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
     268             :         }
     269             : 
     270         119 :         m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     271         119 :         poSpatialRef->Reference();
     272             :         return std::unique_ptr<OGRSpatialReference,
     273         119 :                                OGRSpatialReferenceReleaser>(poSpatialRef);
     274             :     }
     275             : 
     276        1780 :     CPLString oSQL;
     277         890 :     oSQL.Printf("SELECT srs_name, definition, organization, "
     278             :                 "organization_coordsys_id%s%s "
     279             :                 "FROM gpkg_spatial_ref_sys WHERE "
     280             :                 "srs_id = %d LIMIT 2",
     281         890 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     282         890 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     283             : 
     284        1780 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     285             : 
     286         890 :     if (!oResult || oResult->RowCount() != 1)
     287             :     {
     288          12 :         if (bFallbackToEPSG)
     289             :         {
     290           7 :             CPLDebug("GPKG",
     291             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     292             :                      iSrsId);
     293           7 :             OGRSpatialReference *poSRS = new OGRSpatialReference();
     294           7 :             if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
     295             :             {
     296           5 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     297             :                 return std::unique_ptr<OGRSpatialReference,
     298           5 :                                        OGRSpatialReferenceReleaser>(poSRS);
     299             :             }
     300           2 :             poSRS->Release();
     301             :         }
     302           5 :         else if (bEmitErrorIfNotFound)
     303             :         {
     304           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     305             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     306             :                      iSrsId);
     307           2 :             m_oMapSrsIdToSrs[iSrsId] = nullptr;
     308             :         }
     309           7 :         return nullptr;
     310             :     }
     311             : 
     312         878 :     const char *pszName = oResult->GetValue(0, 0);
     313         878 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     314             :     {
     315         361 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     316         361 :         return nullptr;
     317             :     }
     318         517 :     const char *pszWkt = oResult->GetValue(1, 0);
     319         517 :     if (pszWkt == nullptr)
     320           0 :         return nullptr;
     321         517 :     const char *pszOrganization = oResult->GetValue(2, 0);
     322         517 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     323             :     const char *pszWkt2 =
     324         517 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     325         517 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     326          73 :         pszWkt = pszWkt2;
     327             :     const char *pszCoordinateEpoch =
     328         517 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     329             :     const double dfCoordinateEpoch =
     330         517 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     331             : 
     332         517 :     OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     333         517 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     334             :     // Try to import first from EPSG code, and then from WKT
     335         517 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     336         517 :           EQUAL(pszOrganization, "EPSG") &&
     337         498 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     338           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     339         498 :           GDALGPKGImportFromEPSG(
     340        1034 :               poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
     341          19 :         poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
     342             :     {
     343           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     344             :                  "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
     345             :                  pszWkt);
     346           0 :         delete poSpatialRef;
     347           0 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     348           0 :         return nullptr;
     349             :     }
     350             : 
     351         517 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     352         517 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     353         517 :     m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     354         517 :     poSpatialRef->Reference();
     355             :     return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
     356         517 :         poSpatialRef);
     357             : }
     358             : 
     359         229 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     360             : {
     361         229 :     const char *pszName = oSRS.GetName();
     362         229 :     if (pszName)
     363         229 :         return pszName;
     364             : 
     365             :     // Something odd.  Return empty.
     366           0 :     return "Unnamed SRS";
     367             : }
     368             : 
     369             : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
     370           6 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
     371             :     bool bForceEpoch)
     372             : {
     373           6 :     const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
     374             :     auto oResultTable = SQLQuery(
     375             :         hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
     376          12 :              "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
     377           6 :     if (!oResultTable)
     378           0 :         return false;
     379             : 
     380             :     // Temporary remove foreign key checks
     381             :     const GPKGTemporaryForeignKeyCheckDisabler
     382           6 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
     383             : 
     384           6 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
     385             : 
     386           6 :     if (bRet)
     387             :     {
     388             :         std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
     389             :                           "srs_name TEXT NOT NULL,"
     390             :                           "srs_id INTEGER NOT NULL PRIMARY KEY,"
     391             :                           "organization TEXT NOT NULL,"
     392             :                           "organization_coordsys_id INTEGER NOT NULL,"
     393             :                           "definition TEXT NOT NULL,"
     394             :                           "description TEXT, "
     395           6 :                           "definition_12_063 TEXT NOT NULL");
     396           6 :         if (bAddEpoch)
     397           3 :             osSQL += ", epoch DOUBLE";
     398           6 :         osSQL += ")";
     399           6 :         bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
     400             :     }
     401             : 
     402           6 :     if (bRet)
     403             :     {
     404          28 :         for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
     405             :         {
     406          22 :             const char *pszSrsName = oResultTable->GetValue(0, i);
     407          22 :             const char *pszSrsId = oResultTable->GetValue(1, i);
     408          22 :             const char *pszOrganization = oResultTable->GetValue(2, i);
     409             :             const char *pszOrganizationCoordsysID =
     410          22 :                 oResultTable->GetValue(3, i);
     411          22 :             const char *pszDefinition = oResultTable->GetValue(4, i);
     412             :             if (pszSrsName == nullptr || pszSrsId == nullptr ||
     413             :                 pszOrganization == nullptr ||
     414             :                 pszOrganizationCoordsysID == nullptr)
     415             :             {
     416             :                 // should not happen as there are NOT NULL constraints
     417             :                 // But a database could lack such NOT NULL constraints or have
     418             :                 // large values that would cause a memory allocation failure.
     419             :             }
     420          22 :             const char *pszDescription = oResultTable->GetValue(5, i);
     421             :             char *pszSQL;
     422             : 
     423          44 :             OGRSpatialReference oSRS;
     424          22 :             if (pszOrganization && pszOrganizationCoordsysID &&
     425          22 :                 EQUAL(pszOrganization, "EPSG"))
     426             :             {
     427           8 :                 oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
     428             :             }
     429          30 :             if (!oSRS.IsEmpty() && pszDefinition &&
     430           8 :                 !EQUAL(pszDefinition, "undefined"))
     431             :             {
     432           8 :                 oSRS.SetFromUserInput(pszDefinition);
     433             :             }
     434          22 :             char *pszWKT2 = nullptr;
     435          22 :             if (!oSRS.IsEmpty())
     436             :             {
     437           8 :                 const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
     438             :                                                        nullptr};
     439           8 :                 oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
     440           8 :                 if (pszWKT2 && pszWKT2[0] == '\0')
     441             :                 {
     442           0 :                     CPLFree(pszWKT2);
     443           0 :                     pszWKT2 = nullptr;
     444             :                 }
     445             :             }
     446          22 :             if (pszWKT2 == nullptr)
     447             :             {
     448          14 :                 pszWKT2 = CPLStrdup("undefined");
     449             :             }
     450             : 
     451          22 :             if (pszDescription)
     452             :             {
     453          19 :                 pszSQL = sqlite3_mprintf(
     454             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     455             :                     "organization, organization_coordsys_id, definition, "
     456             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     457             :                     "'%q', '%q', '%q', '%q')",
     458             :                     pszSrsName, pszSrsId, pszOrganization,
     459             :                     pszOrganizationCoordsysID, pszDefinition, pszDescription,
     460             :                     pszWKT2);
     461             :             }
     462             :             else
     463             :             {
     464           3 :                 pszSQL = sqlite3_mprintf(
     465             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     466             :                     "organization, organization_coordsys_id, definition, "
     467             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     468             :                     "'%q', '%q', NULL, '%q')",
     469             :                     pszSrsName, pszSrsId, pszOrganization,
     470             :                     pszOrganizationCoordsysID, pszDefinition, pszWKT2);
     471             :             }
     472             : 
     473          22 :             CPLFree(pszWKT2);
     474          22 :             bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
     475          22 :             sqlite3_free(pszSQL);
     476             :         }
     477             :     }
     478             : 
     479           6 :     if (bRet)
     480             :     {
     481           6 :         bRet =
     482           6 :             SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
     483             :     }
     484           6 :     if (bRet)
     485             :     {
     486           6 :         bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
     487             :                                "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
     488             :     }
     489           6 :     if (bRet)
     490             :     {
     491          12 :         bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
     492           6 :                OGRERR_NONE == SQLCommand(hDB,
     493             :                                          "INSERT INTO gpkg_extensions "
     494             :                                          "(table_name, column_name, "
     495             :                                          "extension_name, definition, scope) "
     496             :                                          "VALUES "
     497             :                                          "('gpkg_spatial_ref_sys', "
     498             :                                          "'definition_12_063', 'gpkg_crs_wkt', "
     499             :                                          "'http://www.geopackage.org/spec120/"
     500             :                                          "#extension_crs_wkt', 'read-write')");
     501             :     }
     502           6 :     if (bRet && bAddEpoch)
     503             :     {
     504           3 :         bRet =
     505             :             OGRERR_NONE ==
     506           3 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     507             :                                 "'gpkg_crs_wkt_1_1' "
     508           6 :                                 "WHERE extension_name = 'gpkg_crs_wkt'") &&
     509             :             OGRERR_NONE ==
     510           3 :                 SQLCommand(
     511             :                     hDB,
     512             :                     "INSERT INTO gpkg_extensions "
     513             :                     "(table_name, column_name, extension_name, definition, "
     514             :                     "scope) "
     515             :                     "VALUES "
     516             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     517             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     518             :                     "'read-write')");
     519             :     }
     520           6 :     if (bRet)
     521             :     {
     522           6 :         SoftCommitTransaction();
     523           6 :         m_bHasDefinition12_063 = true;
     524           6 :         if (bAddEpoch)
     525           3 :             m_bHasEpochColumn = true;
     526             :     }
     527             :     else
     528             :     {
     529           0 :         SoftRollbackTransaction();
     530             :     }
     531             : 
     532           6 :     return bRet;
     533             : }
     534             : 
     535         738 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     536             : {
     537         738 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     538        1080 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     539         342 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     540             :     {
     541         398 :         OGRErr err = OGRERR_NONE;
     542         398 :         const int nSRSId = SQLGetInteger(
     543             :             hDB,
     544             :             "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
     545             :             "'Undefined SRS' AND organization = 'GDAL'",
     546             :             &err);
     547         398 :         if (err == OGRERR_NONE)
     548          52 :             return nSRSId;
     549             : 
     550             :         // The below WKT definitions are somehow questionable (using a unknown
     551             :         // unit). For GDAL >= 3.9, they won't be used. They will only be used
     552             :         // for earlier versions.
     553             :         const char *pszSQL;
     554             : #define UNDEFINED_CRS_SRS_ID 99999
     555             :         static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
     556             : #define STRINGIFY(x) #x
     557             : #define XSTRINGIFY(x) STRINGIFY(x)
     558         346 :         if (m_bHasDefinition12_063)
     559             :         {
     560             :             /* clang-format off */
     561           1 :             pszSQL =
     562             :                 "INSERT INTO gpkg_spatial_ref_sys "
     563             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     564             :                 "definition, definition_12_063, description) VALUES "
     565             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     566             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     567             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     568             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     569             :                 "AXIS[\"Northing\",NORTH]]',"
     570             :                 "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
     571             :                 "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
     572             :                 "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
     573             :                 "'Custom undefined coordinate reference system')";
     574             :             /* clang-format on */
     575             :         }
     576             :         else
     577             :         {
     578             :             /* clang-format off */
     579         345 :             pszSQL =
     580             :                 "INSERT INTO gpkg_spatial_ref_sys "
     581             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     582             :                 "definition, description) VALUES "
     583             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     584             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     585             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     586             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     587             :                 "AXIS[\"Northing\",NORTH]]',"
     588             :                 "'Custom undefined coordinate reference system')";
     589             :             /* clang-format on */
     590             :         }
     591         346 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     592         346 :             return UNDEFINED_CRS_SRS_ID;
     593             : #undef UNDEFINED_CRS_SRS_ID
     594             : #undef XSTRINGIFY
     595             : #undef STRINGIFY
     596           0 :         return -1;
     597             :     }
     598             : 
     599         680 :     std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
     600             : 
     601         340 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     602             :     {
     603             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     604         134 :         if (pszName != nullptr && strlen(pszName) > 0)
     605             :         {
     606         134 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     607           2 :                 return 0;
     608             : 
     609         132 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     610           1 :                 return -1;
     611             :         }
     612             :     }
     613             : 
     614         337 :     const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     615             : 
     616         337 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     617             :     {
     618             :         // Try to force identify an EPSG code.
     619          24 :         poSRS->AutoIdentifyEPSG();
     620             : 
     621          24 :         pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     622          24 :         if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
     623             :         {
     624           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     625           0 :             if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
     626             :             {
     627             :                 /* Import 'clean' SRS */
     628           0 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
     629             : 
     630           0 :                 pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     631             :             }
     632             :         }
     633             : 
     634          24 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     635             :     }
     636             : 
     637             :     // Check whether the EPSG authority code is already mapped to a
     638             :     // SRS ID.
     639         337 :     char *pszSQL = nullptr;
     640         337 :     int nSRSId = DEFAULT_SRID;
     641         337 :     int nAuthorityCode = 0;
     642         337 :     OGRErr err = OGRERR_NONE;
     643         337 :     bool bCanUseAuthorityCode = false;
     644         337 :     const char *const apszIsSameOptions[] = {
     645             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     646             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     647         337 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     648             :     {
     649         313 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     650         313 :         if (pszAuthorityCode)
     651             :         {
     652         313 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     653             :             {
     654         313 :                 nAuthorityCode = atoi(pszAuthorityCode);
     655             :             }
     656             :             else
     657             :             {
     658           0 :                 CPLDebug("GPKG",
     659             :                          "SRS has %s:%s identification, but the code not "
     660             :                          "being an integer value cannot be stored as such "
     661             :                          "in the database.",
     662             :                          pszAuthorityName, pszAuthorityCode);
     663           0 :                 pszAuthorityName = nullptr;
     664             :             }
     665             :         }
     666             :     }
     667             : 
     668         650 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     669         313 :         poSRSIn->GetCoordinateEpoch() == 0)
     670             :     {
     671             :         pszSQL =
     672         308 :             sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     673             :                             "upper(organization) = upper('%q') AND "
     674             :                             "organization_coordsys_id = %d",
     675             :                             pszAuthorityName, nAuthorityCode);
     676             : 
     677         308 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     678         308 :         sqlite3_free(pszSQL);
     679             : 
     680             :         // Got a match? Return it!
     681         308 :         if (OGRERR_NONE == err)
     682             :         {
     683         104 :             auto poRefSRS = GetSpatialRef(nSRSId);
     684             :             bool bOK =
     685         104 :                 (poRefSRS == nullptr ||
     686         105 :                  poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
     687           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     688         104 :             if (bOK)
     689             :             {
     690         103 :                 return nSRSId;
     691             :             }
     692             :             else
     693             :             {
     694           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     695             :                          "Passed SRS uses %s:%d identification, but its "
     696             :                          "definition is not compatible with the "
     697             :                          "definition of that object already in the database. "
     698             :                          "Registering it as a new entry into the database.",
     699             :                          pszAuthorityName, nAuthorityCode);
     700           1 :                 pszAuthorityName = nullptr;
     701           1 :                 nAuthorityCode = 0;
     702             :             }
     703             :         }
     704             :     }
     705             : 
     706             :     // Translate SRS to WKT.
     707         234 :     CPLCharUniquePtr pszWKT1;
     708         234 :     CPLCharUniquePtr pszWKT2_2015;
     709         234 :     CPLCharUniquePtr pszWKT2_2019;
     710         234 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     711         234 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     712         234 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     713             : 
     714         468 :     std::string osEpochTest;
     715         234 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     716             :     {
     717             :         osEpochTest =
     718           3 :             CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
     719             :     }
     720             : 
     721         234 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
     722             :     {
     723         227 :         char *pszTmp = nullptr;
     724         227 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     725         227 :         pszWKT1.reset(pszTmp);
     726         227 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     727             :         {
     728           0 :             pszWKT1.reset();
     729             :         }
     730             :     }
     731             :     {
     732         234 :         char *pszTmp = nullptr;
     733         234 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     734         234 :         pszWKT2_2015.reset(pszTmp);
     735         234 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     736             :         {
     737           0 :             pszWKT2_2015.reset();
     738             :         }
     739             :     }
     740             :     {
     741         234 :         char *pszTmp = nullptr;
     742         234 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     743         234 :         pszWKT2_2019.reset(pszTmp);
     744         234 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     745             :         {
     746           0 :             pszWKT2_2019.reset();
     747             :         }
     748             :     }
     749             : 
     750         234 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     751             :     {
     752           0 :         return DEFAULT_SRID;
     753             :     }
     754             : 
     755         234 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     756             :     {
     757             :         // Search if there is already an existing entry with this WKT
     758         231 :         if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
     759             :         {
     760          40 :             if (pszWKT1)
     761             :             {
     762         140 :                 pszSQL = sqlite3_mprintf(
     763             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     764             :                     "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
     765             :                     pszWKT1.get(),
     766          70 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     767          70 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     768             :                     osEpochTest.c_str());
     769             :             }
     770             :             else
     771             :             {
     772          20 :                 pszSQL = sqlite3_mprintf(
     773             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     774             :                     "definition_12_063 IN ('%q', '%q')%s",
     775          10 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     776          10 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     777             :                     osEpochTest.c_str());
     778             :             }
     779             :         }
     780         191 :         else if (pszWKT1)
     781             :         {
     782             :             pszSQL =
     783         189 :                 sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     784             :                                 "definition = '%q'%s",
     785             :                                 pszWKT1.get(), osEpochTest.c_str());
     786             :         }
     787             :         else
     788             :         {
     789           2 :             pszSQL = nullptr;
     790             :         }
     791         231 :         if (pszSQL)
     792             :         {
     793         229 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     794         229 :             sqlite3_free(pszSQL);
     795         229 :             if (OGRERR_NONE == err)
     796             :             {
     797           5 :                 return nSRSId;
     798             :             }
     799             :         }
     800             :     }
     801             : 
     802         436 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     803         207 :         poSRSIn->GetCoordinateEpoch() == 0)
     804             :     {
     805         203 :         bool bTryToReuseSRSId = true;
     806         203 :         if (EQUAL(pszAuthorityName, "EPSG"))
     807             :         {
     808         404 :             OGRSpatialReference oSRS_EPSG;
     809         202 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     810             :                 OGRERR_NONE)
     811             :             {
     812         203 :                 if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
     813           1 :                     CPLTestBool(
     814             :                         CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
     815             :                 {
     816           1 :                     bTryToReuseSRSId = false;
     817           1 :                     CPLError(
     818             :                         CE_Warning, CPLE_AppDefined,
     819             :                         "Passed SRS uses %s:%d identification, but its "
     820             :                         "definition is not compatible with the "
     821             :                         "official definition of the object. "
     822             :                         "Registering it as a non-%s entry into the database.",
     823             :                         pszAuthorityName, nAuthorityCode, pszAuthorityName);
     824           1 :                     pszAuthorityName = nullptr;
     825           1 :                     nAuthorityCode = 0;
     826             :                 }
     827             :             }
     828             :         }
     829         203 :         if (bTryToReuseSRSId)
     830             :         {
     831             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     832         202 :             pszSQL = sqlite3_mprintf(
     833             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     834             :                 "srs_id = %d",
     835             :                 nAuthorityCode);
     836             : 
     837             :             // Yep, we can!
     838         202 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     839         201 :                 bCanUseAuthorityCode = true;
     840         202 :             sqlite3_free(pszSQL);
     841             :         }
     842             :     }
     843             : 
     844         229 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     845         229 :     bool bForceEpoch = false;
     846         231 :     if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
     847           2 :         (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
     848             :     {
     849           2 :         bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     850             :     }
     851             : 
     852             :     // Add epoch column if needed
     853         229 :     if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
     854             :     {
     855           3 :         if (m_bHasDefinition12_063)
     856             :         {
     857           0 :             if (SoftStartTransaction() != OGRERR_NONE)
     858           0 :                 return DEFAULT_SRID;
     859           0 :             if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
     860           0 :                                 "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
     861           0 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     862             :                                 "'gpkg_crs_wkt_1_1' "
     863             :                                 "WHERE extension_name = 'gpkg_crs_wkt'") !=
     864           0 :                     OGRERR_NONE ||
     865           0 :                 SQLCommand(
     866             :                     hDB,
     867             :                     "INSERT INTO gpkg_extensions "
     868             :                     "(table_name, column_name, extension_name, definition, "
     869             :                     "scope) "
     870             :                     "VALUES "
     871             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     872             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     873             :                     "'read-write')") != OGRERR_NONE)
     874             :             {
     875           0 :                 SoftRollbackTransaction();
     876           0 :                 return DEFAULT_SRID;
     877             :             }
     878             : 
     879           0 :             if (SoftCommitTransaction() != OGRERR_NONE)
     880           0 :                 return DEFAULT_SRID;
     881             : 
     882           0 :             m_bHasEpochColumn = true;
     883             :         }
     884             :         else
     885             :         {
     886           3 :             bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     887           3 :             bForceEpoch = true;
     888             :         }
     889             :     }
     890             : 
     891         234 :     if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
     892           5 :         !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
     893             :     {
     894           0 :         return DEFAULT_SRID;
     895             :     }
     896             : 
     897             :     // Reuse the authority code number as SRS_ID if we can
     898         229 :     if (bCanUseAuthorityCode)
     899             :     {
     900         201 :         nSRSId = nAuthorityCode;
     901             :     }
     902             :     // Otherwise, generate a new SRS_ID number (max + 1)
     903             :     else
     904             :     {
     905             :         // Get the current maximum srid in the srs table.
     906          28 :         const int nMaxSRSId = SQLGetInteger(
     907             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     908          28 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     909             :     }
     910             : 
     911         458 :     std::string osEpochColumn;
     912         229 :     std::string osEpochVal;
     913         229 :     if (poSRSIn->GetCoordinateEpoch() > 0)
     914             :     {
     915           5 :         osEpochColumn = ", epoch";
     916           5 :         osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
     917             :     }
     918             : 
     919             :     // Add new SRS row to gpkg_spatial_ref_sys.
     920         229 :     if (m_bHasDefinition12_063)
     921             :     {
     922             :         // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
     923          42 :         const char *pszWKT2 = poSRSIn->IsDynamic() &&
     924           8 :                                       poSRSIn->GetCoordinateEpoch() > 0 &&
     925           1 :                                       pszWKT2_2019
     926           1 :                                   ? pszWKT2_2019.get()
     927          41 :                               : pszWKT2_2015 ? pszWKT2_2015.get()
     928          89 :                                              : pszWKT2_2019.get();
     929             : 
     930          42 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     931             :         {
     932          93 :             pszSQL = sqlite3_mprintf(
     933             :                 "INSERT INTO gpkg_spatial_ref_sys "
     934             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     935             :                 "definition, definition_12_063%s) VALUES "
     936             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     937          31 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
     938             :                 pszAuthorityName, nAuthorityCode,
     939          59 :                 pszWKT1 ? pszWKT1.get() : "undefined",
     940             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     941             :         }
     942             :         else
     943             :         {
     944          33 :             pszSQL = sqlite3_mprintf(
     945             :                 "INSERT INTO gpkg_spatial_ref_sys "
     946             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     947             :                 "definition, definition_12_063%s) VALUES "
     948             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     949          11 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
     950          20 :                 nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
     951             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     952             :         }
     953             :     }
     954             :     else
     955             :     {
     956         187 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     957             :         {
     958         350 :             pszSQL = sqlite3_mprintf(
     959             :                 "INSERT INTO gpkg_spatial_ref_sys "
     960             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     961             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     962         175 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     963         350 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     964             :         }
     965             :         else
     966             :         {
     967          24 :             pszSQL = sqlite3_mprintf(
     968             :                 "INSERT INTO gpkg_spatial_ref_sys "
     969             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     970             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     971          12 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     972          24 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     973             :         }
     974             :     }
     975             : 
     976             :     // Add new row to gpkg_spatial_ref_sys.
     977         229 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     978             : 
     979             :     // Free everything that was allocated.
     980         229 :     sqlite3_free(pszSQL);
     981             : 
     982         229 :     return nSRSId;
     983             : }
     984             : 
     985             : /************************************************************************/
     986             : /*                        GDALGeoPackageDataset()                       */
     987             : /************************************************************************/
     988             : 
     989        2171 : GDALGeoPackageDataset::GDALGeoPackageDataset()
     990             :     : m_nApplicationId(GPKG_APPLICATION_ID), m_nUserVersion(GPKG_1_2_VERSION),
     991             :       m_papoLayers(nullptr), m_nLayers(0),
     992             : #ifdef ENABLE_GPKG_OGR_CONTENTS
     993             :       m_bHasGPKGOGRContents(false),
     994             : #endif
     995             :       m_bHasGPKGGeometryColumns(false), m_bHasDefinition12_063(false),
     996             :       m_bIdentifierAsCO(false), m_bDescriptionAsCO(false),
     997             :       m_bHasReadMetadataFromStorage(false), m_bMetadataDirty(false),
     998             :       m_bRecordInsertedInGPKGContent(false), m_bGeoTransformValid(false),
     999             :       m_nSRID(-1),  // Unknown Cartesian.
    1000             :       m_dfTMSMinX(0.0), m_dfTMSMaxY(0.0), m_nOverviewCount(0),
    1001             :       m_papoOverviewDS(nullptr), m_bZoomOther(false), m_bInFlushCache(false),
    1002             :       m_osTilingScheme("CUSTOM"), m_bMapTableToExtensionsBuilt(false),
    1003        2171 :       m_bMapTableToContentsBuilt(false)
    1004             : {
    1005        2171 :     m_adfGeoTransform[0] = 0.0;
    1006        2171 :     m_adfGeoTransform[1] = 1.0;
    1007        2171 :     m_adfGeoTransform[2] = 0.0;
    1008        2171 :     m_adfGeoTransform[3] = 0.0;
    1009        2171 :     m_adfGeoTransform[4] = 0.0;
    1010        2171 :     m_adfGeoTransform[5] = 1.0;
    1011        2171 : }
    1012             : 
    1013             : /************************************************************************/
    1014             : /*                       ~GDALGeoPackageDataset()                       */
    1015             : /************************************************************************/
    1016             : 
    1017        4342 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
    1018             : {
    1019        2171 :     GDALGeoPackageDataset::Close();
    1020        4342 : }
    1021             : 
    1022             : /************************************************************************/
    1023             : /*                              Close()                                 */
    1024             : /************************************************************************/
    1025             : 
    1026        3601 : CPLErr GDALGeoPackageDataset::Close()
    1027             : {
    1028        3601 :     CPLErr eErr = CE_None;
    1029        3601 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    1030             :     {
    1031        1264 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
    1032        3435 :             !m_osRasterTable.empty() && !m_bGeoTransformValid)
    1033             :         {
    1034           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    1035             :                      "Raster table %s not correctly initialized due to missing "
    1036             :                      "call to SetGeoTransform()",
    1037             :                      m_osRasterTable.c_str());
    1038             :         }
    1039             : 
    1040        2171 :         if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
    1041           7 :             eErr = CE_Failure;
    1042             : 
    1043             :         // Destroy bands now since we don't want
    1044             :         // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
    1045             :         // destruction
    1046        3944 :         for (int i = 0; i < nBands; i++)
    1047        1773 :             delete papoBands[i];
    1048        2171 :         nBands = 0;
    1049        2171 :         CPLFree(papoBands);
    1050        2171 :         papoBands = nullptr;
    1051             : 
    1052             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1053             :         // need it
    1054        2496 :         for (int i = 0; i < m_nOverviewCount; i++)
    1055         325 :             delete m_papoOverviewDS[i];
    1056             : 
    1057        2171 :         if (m_poParentDS)
    1058             :         {
    1059         325 :             hDB = nullptr;
    1060             :         }
    1061             : 
    1062        5720 :         for (int i = 0; i < m_nLayers; i++)
    1063        3549 :             delete m_papoLayers[i];
    1064             : 
    1065        2171 :         CPLFree(m_papoLayers);
    1066        2171 :         CPLFree(m_papoOverviewDS);
    1067             : 
    1068             :         std::map<int, OGRSpatialReference *>::iterator oIter =
    1069        2171 :             m_oMapSrsIdToSrs.begin();
    1070        3170 :         for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
    1071             :         {
    1072         999 :             OGRSpatialReference *poSRS = oIter->second;
    1073         999 :             if (poSRS)
    1074         636 :                 poSRS->Release();
    1075             :         }
    1076             : 
    1077        2171 :         if (!CloseDB())
    1078           0 :             eErr = CE_Failure;
    1079             : 
    1080        2171 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1081           0 :             eErr = CE_Failure;
    1082             :     }
    1083        3601 :     return eErr;
    1084             : }
    1085             : 
    1086             : /************************************************************************/
    1087             : /*                         ICanIWriteBlock()                            */
    1088             : /************************************************************************/
    1089             : 
    1090        5677 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1091             : {
    1092        5677 :     if (!GetUpdate())
    1093             :     {
    1094           0 :         CPLError(
    1095             :             CE_Failure, CPLE_NotSupported,
    1096             :             "IWriteBlock() not supported on dataset opened in read-only mode");
    1097           0 :         return false;
    1098             :     }
    1099             : 
    1100        5677 :     if (m_pabyCachedTiles == nullptr)
    1101             :     {
    1102           0 :         return false;
    1103             :     }
    1104             : 
    1105        5677 :     if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
    1106             :     {
    1107           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1108             :                  "IWriteBlock() not supported if georeferencing not set");
    1109           0 :         return false;
    1110             :     }
    1111        5677 :     return true;
    1112             : }
    1113             : 
    1114             : /************************************************************************/
    1115             : /*                            IRasterIO()                               */
    1116             : /************************************************************************/
    1117             : 
    1118         114 : CPLErr GDALGeoPackageDataset::IRasterIO(
    1119             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1120             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1121             :     int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
    1122             :     GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1123             : 
    1124             : {
    1125         114 :     CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
    1126             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1127             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1128             :         psExtraArg);
    1129             : 
    1130             :     // If writing all bands, in non-shifted mode, flush all entirely written
    1131             :     // tiles This can avoid "stressing" the block cache with too many dirty
    1132             :     // blocks. Note: this logic would be useless with a per-dataset block cache.
    1133         114 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1134         105 :         nYSize == nBufYSize && nBandCount == nBands &&
    1135         102 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1136             :     {
    1137             :         auto poBand =
    1138          98 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1139             :         int nBlockXSize, nBlockYSize;
    1140          98 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1141          98 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1142          98 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1143          98 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1144          98 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1145         252 :         for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
    1146             :         {
    1147        4371 :             for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
    1148             :             {
    1149             :                 GDALRasterBlock *poBlock =
    1150        4217 :                     poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
    1151        4217 :                 if (poBlock)
    1152             :                 {
    1153             :                     // GetDirty() should be true in most situation (otherwise
    1154             :                     // it means the block cache is under extreme pressure!)
    1155        4215 :                     if (poBlock->GetDirty())
    1156             :                     {
    1157             :                         // IWriteBlock() on one band will check the dirty state
    1158             :                         // of the corresponding blocks in other bands, to decide
    1159             :                         // if it can call WriteTile(), so we have only to do
    1160             :                         // that on one of the bands
    1161        4215 :                         if (poBlock->Write() != CE_None)
    1162         250 :                             eErr = CE_Failure;
    1163             :                     }
    1164        4215 :                     poBlock->DropLock();
    1165             :                 }
    1166             :             }
    1167             :         }
    1168             :     }
    1169             : 
    1170         114 :     return eErr;
    1171             : }
    1172             : 
    1173             : /************************************************************************/
    1174             : /*                          GetOGRTableLimit()                          */
    1175             : /************************************************************************/
    1176             : 
    1177        3441 : static int GetOGRTableLimit()
    1178             : {
    1179        3441 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1180             : }
    1181             : 
    1182             : /************************************************************************/
    1183             : /*                      GetNameTypeMapFromSQliteMaster()                */
    1184             : /************************************************************************/
    1185             : 
    1186             : const std::map<CPLString, CPLString> &
    1187        1056 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1188             : {
    1189        1056 :     if (!m_oMapNameToType.empty())
    1190         294 :         return m_oMapNameToType;
    1191             : 
    1192             :     CPLString osSQL(
    1193             :         "SELECT name, type FROM sqlite_master WHERE "
    1194             :         "type IN ('view', 'table') OR "
    1195        1524 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1196         762 :     const int nTableLimit = GetOGRTableLimit();
    1197         762 :     if (nTableLimit > 0)
    1198             :     {
    1199         762 :         osSQL += " LIMIT ";
    1200         762 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1201             :     }
    1202             : 
    1203         762 :     auto oResult = SQLQuery(hDB, osSQL);
    1204         762 :     if (oResult)
    1205             :     {
    1206       12686 :         for (int i = 0; i < oResult->RowCount(); i++)
    1207             :         {
    1208       11924 :             const char *pszName = oResult->GetValue(0, i);
    1209       11924 :             const char *pszType = oResult->GetValue(1, i);
    1210       11924 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1211             :         }
    1212             :     }
    1213             : 
    1214         762 :     return m_oMapNameToType;
    1215             : }
    1216             : 
    1217             : /************************************************************************/
    1218             : /*                    RemoveTableFromSQLiteMasterCache()                */
    1219             : /************************************************************************/
    1220             : 
    1221          51 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1222             :     const char *pszTableName)
    1223             : {
    1224          51 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1225          51 : }
    1226             : 
    1227             : /************************************************************************/
    1228             : /*                  GetUnknownExtensionsTableSpecific()                 */
    1229             : /************************************************************************/
    1230             : 
    1231             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1232         728 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1233             : {
    1234         728 :     if (m_bMapTableToExtensionsBuilt)
    1235          68 :         return m_oMapTableToExtensions;
    1236         660 :     m_bMapTableToExtensionsBuilt = true;
    1237             : 
    1238         660 :     if (!HasExtensionsTable())
    1239          40 :         return m_oMapTableToExtensions;
    1240             : 
    1241             :     CPLString osSQL(
    1242             :         "SELECT table_name, extension_name, definition, scope "
    1243             :         "FROM gpkg_extensions WHERE "
    1244             :         "table_name IS NOT NULL "
    1245             :         "AND extension_name IS NOT NULL "
    1246             :         "AND definition IS NOT NULL "
    1247             :         "AND scope IS NOT NULL "
    1248             :         "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
    1249             :         "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
    1250             :         "'gpkg_geom_MULTICURVE', "
    1251             :         "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
    1252             :         "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
    1253             :         "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
    1254             :         "'gpkg_srs_id_trigger', "
    1255             :         "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
    1256             :         "'gpkg_related_tables', 'related_tables'"
    1257             : #ifdef HAVE_SPATIALITE
    1258             :         ", 'gdal_spatialite_computed_geom_column'"
    1259             : #endif
    1260        1240 :         ")");
    1261         620 :     const int nTableLimit = GetOGRTableLimit();
    1262         620 :     if (nTableLimit > 0)
    1263             :     {
    1264         620 :         osSQL += " LIMIT ";
    1265         620 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1266             :     }
    1267             : 
    1268         620 :     auto oResult = SQLQuery(hDB, osSQL);
    1269         620 :     if (oResult)
    1270             :     {
    1271        1199 :         for (int i = 0; i < oResult->RowCount(); i++)
    1272             :         {
    1273         579 :             const char *pszTableName = oResult->GetValue(0, i);
    1274         579 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1275         579 :             const char *pszDefinition = oResult->GetValue(2, i);
    1276         579 :             const char *pszScope = oResult->GetValue(3, i);
    1277         579 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1278             :             {
    1279         579 :                 GPKGExtensionDesc oDesc;
    1280         579 :                 oDesc.osExtensionName = pszExtensionName;
    1281         579 :                 oDesc.osDefinition = pszDefinition;
    1282         579 :                 oDesc.osScope = pszScope;
    1283        1158 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1284         579 :                     .push_back(oDesc);
    1285             :             }
    1286             :         }
    1287             :     }
    1288             : 
    1289         620 :     return m_oMapTableToExtensions;
    1290             : }
    1291             : 
    1292             : /************************************************************************/
    1293             : /*                           GetContents()                              */
    1294             : /************************************************************************/
    1295             : 
    1296             : const std::map<CPLString, GPKGContentsDesc> &
    1297         714 : GDALGeoPackageDataset::GetContents()
    1298             : {
    1299         714 :     if (m_bMapTableToContentsBuilt)
    1300          56 :         return m_oMapTableToContents;
    1301         658 :     m_bMapTableToContentsBuilt = true;
    1302             : 
    1303             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1304             :                     "description, min_x, min_y, max_x, max_y "
    1305        1316 :                     "FROM gpkg_contents");
    1306         658 :     const int nTableLimit = GetOGRTableLimit();
    1307         658 :     if (nTableLimit > 0)
    1308             :     {
    1309         658 :         osSQL += " LIMIT ";
    1310         658 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1311             :     }
    1312             : 
    1313         658 :     auto oResult = SQLQuery(hDB, osSQL);
    1314         658 :     if (oResult)
    1315             :     {
    1316        1411 :         for (int i = 0; i < oResult->RowCount(); i++)
    1317             :         {
    1318         753 :             const char *pszTableName = oResult->GetValue(0, i);
    1319         753 :             if (pszTableName == nullptr)
    1320           0 :                 continue;
    1321         753 :             const char *pszDataType = oResult->GetValue(1, i);
    1322         753 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1323         753 :             const char *pszDescription = oResult->GetValue(3, i);
    1324         753 :             const char *pszMinX = oResult->GetValue(4, i);
    1325         753 :             const char *pszMinY = oResult->GetValue(5, i);
    1326         753 :             const char *pszMaxX = oResult->GetValue(6, i);
    1327         753 :             const char *pszMaxY = oResult->GetValue(7, i);
    1328         753 :             GPKGContentsDesc oDesc;
    1329         753 :             if (pszDataType)
    1330         753 :                 oDesc.osDataType = pszDataType;
    1331         753 :             if (pszIdentifier)
    1332         753 :                 oDesc.osIdentifier = pszIdentifier;
    1333         753 :             if (pszDescription)
    1334         752 :                 oDesc.osDescription = pszDescription;
    1335         753 :             if (pszMinX)
    1336         527 :                 oDesc.osMinX = pszMinX;
    1337         753 :             if (pszMinY)
    1338         527 :                 oDesc.osMinY = pszMinY;
    1339         753 :             if (pszMaxX)
    1340         527 :                 oDesc.osMaxX = pszMaxX;
    1341         753 :             if (pszMaxY)
    1342         527 :                 oDesc.osMaxY = pszMaxY;
    1343        1506 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1344        1506 :                 std::move(oDesc);
    1345             :         }
    1346             :     }
    1347             : 
    1348         658 :     return m_oMapTableToContents;
    1349             : }
    1350             : 
    1351             : /************************************************************************/
    1352             : /*                                Open()                                */
    1353             : /************************************************************************/
    1354             : 
    1355        1071 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1356             :                                 const std::string &osFilenameInZip)
    1357             : {
    1358        1071 :     m_osFilenameInZip = osFilenameInZip;
    1359        1071 :     CPLAssert(m_nLayers == 0);
    1360        1071 :     CPLAssert(hDB == nullptr);
    1361             : 
    1362        1071 :     SetDescription(poOpenInfo->pszFilename);
    1363        2142 :     CPLString osFilename(poOpenInfo->pszFilename);
    1364        2142 :     CPLString osSubdatasetTableName;
    1365             :     GByte abyHeaderLetMeHerePlease[100];
    1366        1071 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1367        1071 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
    1368             :     {
    1369         240 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    1370             :                                                 CSLT_HONOURSTRINGS);
    1371         240 :         int nCount = CSLCount(papszTokens);
    1372         240 :         if (nCount < 2)
    1373             :         {
    1374           0 :             CSLDestroy(papszTokens);
    1375           0 :             return FALSE;
    1376             :         }
    1377             : 
    1378         240 :         if (nCount <= 3)
    1379             :         {
    1380         238 :             osFilename = papszTokens[1];
    1381             :         }
    1382             :         /* GPKG:C:\BLA.GPKG:foo */
    1383           2 :         else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
    1384           2 :                  (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
    1385             :         {
    1386           2 :             osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1387             :         }
    1388             :         // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
    1389           0 :         else if (/*nCount >= 4 && */
    1390           0 :                  (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1391           0 :                   EQUAL(papszTokens[1], "/vsicurl/https")))
    1392             :         {
    1393           0 :             osFilename = CPLString(papszTokens[1]);
    1394           0 :             for (int i = 2; i < nCount - 1; i++)
    1395             :             {
    1396           0 :                 osFilename += ':';
    1397           0 :                 osFilename += papszTokens[i];
    1398             :             }
    1399             :         }
    1400         240 :         if (nCount >= 3)
    1401          12 :             osSubdatasetTableName = papszTokens[nCount - 1];
    1402             : 
    1403         240 :         CSLDestroy(papszTokens);
    1404         240 :         VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    1405         240 :         if (fp != nullptr)
    1406             :         {
    1407         240 :             VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
    1408         240 :             VSIFCloseL(fp);
    1409             :         }
    1410         240 :         pabyHeader = abyHeaderLetMeHerePlease;
    1411             :     }
    1412         831 :     else if (poOpenInfo->pabyHeader &&
    1413         831 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1414             :                          "SQLite format 3"))
    1415             :     {
    1416         825 :         m_bCallUndeclareFileNotToOpen = true;
    1417         825 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1418             :                                          poOpenInfo->nHeaderBytes);
    1419             :     }
    1420             : 
    1421        1071 :     eAccess = poOpenInfo->eAccess;
    1422        1071 :     if (!m_osFilenameInZip.empty())
    1423             :     {
    1424           1 :         m_pszFilename = CPLStrdup(CPLSPrintf(
    1425             :             "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
    1426             :     }
    1427             :     else
    1428             :     {
    1429        1070 :         m_pszFilename = CPLStrdup(osFilename);
    1430             :     }
    1431             : 
    1432        1071 :     if (poOpenInfo->papszOpenOptions)
    1433             :     {
    1434          98 :         CSLDestroy(papszOpenOptions);
    1435          98 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1436             :     }
    1437             : 
    1438             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1439        1071 :     if (poOpenInfo->pabyHeader &&
    1440         831 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1441           5 :                     "-- SQL GPKG") &&
    1442           5 :         poOpenInfo->fpL != nullptr)
    1443             :     {
    1444           5 :         if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
    1445             :             SQLITE_OK)
    1446             :         {
    1447           0 :             return FALSE;
    1448             :         }
    1449             : 
    1450           5 :         InstallSQLFunctions();
    1451             : 
    1452             :         // Ingest the lines of the dump
    1453           5 :         VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
    1454             :         const char *pszLine;
    1455          76 :         while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
    1456             :         {
    1457          71 :             if (STARTS_WITH(pszLine, "--"))
    1458           5 :                 continue;
    1459             : 
    1460             :             // Reject a few words tat might have security implications
    1461             :             // Basically we just want to allow CREATE TABLE and INSERT INTO
    1462          66 :             if (CPLString(pszLine).ifind("ATTACH") != std::string::npos ||
    1463         132 :                 CPLString(pszLine).ifind("DETACH") != std::string::npos ||
    1464         132 :                 CPLString(pszLine).ifind("PRAGMA") != std::string::npos ||
    1465         132 :                 CPLString(pszLine).ifind("SELECT") != std::string::npos ||
    1466         128 :                 CPLString(pszLine).ifind("UPDATE") != std::string::npos ||
    1467         128 :                 CPLString(pszLine).ifind("REPLACE") != std::string::npos ||
    1468         128 :                 CPLString(pszLine).ifind("DELETE") != std::string::npos ||
    1469         128 :                 CPLString(pszLine).ifind("DROP") != std::string::npos ||
    1470         260 :                 CPLString(pszLine).ifind("ALTER") != std::string::npos ||
    1471         128 :                 CPLString(pszLine).ifind("VIRTUAL") != std::string::npos)
    1472             :             {
    1473           8 :                 bool bOK = false;
    1474             :                 // Accept creation of spatial index
    1475           8 :                 if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
    1476             :                 {
    1477           4 :                     const char *pszStr =
    1478             :                         pszLine + strlen("CREATE VIRTUAL TABLE ");
    1479           4 :                     if (*pszStr == '"')
    1480           0 :                         pszStr++;
    1481          52 :                     while ((*pszStr >= 'a' && *pszStr <= 'z') ||
    1482          64 :                            (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
    1483             :                     {
    1484          60 :                         pszStr++;
    1485             :                     }
    1486           4 :                     if (*pszStr == '"')
    1487           0 :                         pszStr++;
    1488           4 :                     if (EQUAL(pszStr,
    1489             :                               " USING rtree(id, minx, maxx, miny, maxy);"))
    1490             :                     {
    1491           4 :                         bOK = true;
    1492             :                     }
    1493             :                 }
    1494             :                 // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
    1495             :                 // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
    1496           8 :                 else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
    1497           8 :                          CPLString(pszLine).ifind("SELECT") !=
    1498             :                              std::string::npos)
    1499             :                 {
    1500             :                     char **papszTokens =
    1501           4 :                         CSLTokenizeString2(pszLine, " (),,", 0);
    1502           4 :                     if (CSLCount(papszTokens) == 15 &&
    1503           4 :                         EQUAL(papszTokens[3], "SELECT") &&
    1504           4 :                         EQUAL(papszTokens[5], "ST_MinX") &&
    1505           4 :                         EQUAL(papszTokens[7], "ST_MaxX") &&
    1506           4 :                         EQUAL(papszTokens[9], "ST_MinY") &&
    1507          12 :                         EQUAL(papszTokens[11], "ST_MaxY") &&
    1508           4 :                         EQUAL(papszTokens[13], "FROM"))
    1509             :                     {
    1510           4 :                         bOK = TRUE;
    1511             :                     }
    1512           4 :                     CSLDestroy(papszTokens);
    1513             :                 }
    1514             : 
    1515           8 :                 if (!bOK)
    1516             :                 {
    1517           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    1518             :                              "Rejected statement: %s", pszLine);
    1519           0 :                     return FALSE;
    1520             :                 }
    1521             :             }
    1522          66 :             char *pszErrMsg = nullptr;
    1523          66 :             if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
    1524             :                 SQLITE_OK)
    1525             :             {
    1526           0 :                 if (pszErrMsg)
    1527           0 :                     CPLDebug("SQLITE", "Error %s", pszErrMsg);
    1528             :             }
    1529          66 :             sqlite3_free(pszErrMsg);
    1530           5 :         }
    1531             :     }
    1532             : 
    1533        1066 :     else if (pabyHeader != nullptr)
    1534             : #endif
    1535             :     {
    1536        1066 :         if (poOpenInfo->fpL)
    1537             :         {
    1538             :             // See above comment about -wal locking for the importance of
    1539             :             // closing that file, prior to calling sqlite3_open()
    1540         726 :             VSIFCloseL(poOpenInfo->fpL);
    1541         726 :             poOpenInfo->fpL = nullptr;
    1542             :         }
    1543             : 
    1544             :         /* See if we can open the SQLite database */
    1545        1066 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1546             :                                         : SQLITE_OPEN_READONLY))
    1547           2 :             return FALSE;
    1548             : 
    1549        1064 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1550        1064 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1551        1064 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1552        1064 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1553        1064 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1554             :         {
    1555           7 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1556             :         }
    1557        1057 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1558             :         {
    1559           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1560             :         }
    1561        1055 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1562        1052 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1563             :         {
    1564        1050 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1565        1050 :                      (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    1566             :         }
    1567             :     }
    1568             : 
    1569             :     /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
    1570             :      * “ok” */
    1571             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1572             :     /* Disable integrity check by default, since it is expensive on big files */
    1573        1069 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
    1574           0 :         OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
    1575             :     {
    1576           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1577             :                  "pragma integrity_check on '%s' failed", m_pszFilename);
    1578           0 :         return FALSE;
    1579             :     }
    1580             : 
    1581             :     /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
    1582             :     /* parameter value SHALL return an empty result set */
    1583             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1584             :     /* Disable the check by default, since it is to corrupt databases, and */
    1585             :     /* that causes issues to downstream software that can't open them. */
    1586        1069 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
    1587           0 :         OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
    1588             :     {
    1589           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1590             :                  "pragma foreign_key_check on '%s' failed.", m_pszFilename);
    1591           0 :         return FALSE;
    1592             :     }
    1593             : 
    1594             :     /* Check for requirement metadata tables */
    1595             :     /* Requirement 10: gpkg_spatial_ref_sys must exist */
    1596             :     /* Requirement 13: gpkg_contents must exist */
    1597        1069 :     if (SQLGetInteger(hDB,
    1598             :                       "SELECT COUNT(*) FROM sqlite_master WHERE "
    1599             :                       "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
    1600             :                       "type IN ('table', 'view')",
    1601        1069 :                       nullptr) != 2)
    1602             :     {
    1603           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1604             :                  "At least one of the required GeoPackage tables, "
    1605             :                  "gpkg_spatial_ref_sys or gpkg_contents, is missing");
    1606           0 :         return FALSE;
    1607             :     }
    1608             : 
    1609        1069 :     DetectSpatialRefSysColumns();
    1610             : 
    1611             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1612        1069 :     if (SQLGetInteger(hDB,
    1613             :                       "SELECT 1 FROM sqlite_master WHERE "
    1614             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1615        1069 :                       nullptr) == 1)
    1616             :     {
    1617        1064 :         m_bHasGPKGOGRContents = true;
    1618             :     }
    1619             : #endif
    1620             : 
    1621        1069 :     CheckUnknownExtensions();
    1622             : 
    1623        1069 :     int bRet = FALSE;
    1624        1069 :     bool bHasGPKGExtRelations = false;
    1625        1069 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1626             :     {
    1627         886 :         m_bHasGPKGGeometryColumns =
    1628         886 :             SQLGetInteger(hDB,
    1629             :                           "SELECT 1 FROM sqlite_master WHERE "
    1630             :                           "name = 'gpkg_geometry_columns' AND "
    1631             :                           "type IN ('table', 'view')",
    1632         886 :                           nullptr) == 1;
    1633         886 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1634             :     }
    1635        1069 :     if (m_bHasGPKGGeometryColumns)
    1636             :     {
    1637             :         /* Load layer definitions for all tables in gpkg_contents &
    1638             :          * gpkg_geometry_columns */
    1639             :         /* and non-spatial tables as well */
    1640             :         std::string osSQL =
    1641             :             "SELECT c.table_name, c.identifier, 1 as is_spatial, "
    1642             :             "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
    1643             :             "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
    1644             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1645             :             "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
    1646             :             "  FROM gpkg_geometry_columns g "
    1647             :             "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
    1648             :             "  WHERE "
    1649             :             "  c.table_name <> 'ogr_empty_table' AND"
    1650             :             "  c.data_type = 'features' "
    1651             :             // aspatial: Was the only method available in OGR 2.0 and 2.1
    1652             :             // attributes: GPKG 1.2 or later
    1653             :             "UNION ALL "
    1654             :             "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
    1655             :             "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
    1656             :             "is_in_gpkg_contents, "
    1657             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1658             :             "lower(table_name) AND type IN ('table', 'view')) AS object_type "
    1659             :             "  FROM gpkg_contents"
    1660         885 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1661             : 
    1662        1770 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1663         885 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1664         885 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1665         885 :         if (!bHasASpatialOrAttributes)
    1666             :         {
    1667             :             auto oResultTable =
    1668             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1669         884 :                               "data_type = 'attributes' LIMIT 1");
    1670         884 :             bHasASpatialOrAttributes =
    1671         884 :                 (oResultTable && oResultTable->RowCount() == 1);
    1672             :         }
    1673         885 :         if (bHasGPKGExtRelations)
    1674             :         {
    1675             :             osSQL += "UNION ALL "
    1676             :                      "SELECT mapping_table_name, mapping_table_name, 0 as "
    1677             :                      "is_spatial, NULL, NULL, 0, 0, 0 AS "
    1678             :                      "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1679             :                      "is_in_gpkg_contents, 'table' AS object_type "
    1680             :                      "FROM gpkgext_relations WHERE "
    1681             :                      "lower(mapping_table_name) NOT IN (SELECT "
    1682             :                      "lower(table_name) FROM "
    1683          12 :                      "gpkg_contents)";
    1684             :         }
    1685         885 :         if (EQUAL(pszListAllTables, "YES") ||
    1686         885 :             (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
    1687             :         {
    1688             :             // vgpkg_ is Spatialite virtual table
    1689             :             osSQL +=
    1690             :                 "UNION ALL "
    1691             :                 "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
    1692             :                 "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1693             :                 "is_in_gpkg_contents, type AS object_type "
    1694             :                 "FROM sqlite_master WHERE type IN ('table', 'view') "
    1695             :                 "AND name NOT LIKE 'gpkg_%' "
    1696             :                 "AND name NOT LIKE 'vgpkg_%' "
    1697             :                 "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
    1698             :                 // Avoid reading those views from simple_sewer_features.gpkg
    1699             :                 "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
    1700             :                 "'st_geometry_columns', 'geometry_columns') "
    1701             :                 "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
    1702         832 :                 "gpkg_contents)";
    1703         832 :             if (bHasGPKGExtRelations)
    1704             :             {
    1705             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1706             :                          "lower(mapping_table_name) FROM "
    1707           8 :                          "gpkgext_relations)";
    1708             :             }
    1709             :         }
    1710         885 :         const int nTableLimit = GetOGRTableLimit();
    1711         885 :         if (nTableLimit > 0)
    1712             :         {
    1713         885 :             osSQL += " LIMIT ";
    1714         885 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1715             :         }
    1716             : 
    1717         885 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1718         885 :         if (!oResult)
    1719             :         {
    1720           0 :             return FALSE;
    1721             :         }
    1722             : 
    1723         885 :         if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1724             :         {
    1725           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1726             :                      "File has more than %d vector tables. "
    1727             :                      "Limiting to first %d (can be overridden with "
    1728             :                      "OGR_TABLE_LIMIT config option)",
    1729             :                      nTableLimit, nTableLimit);
    1730           1 :             oResult->LimitRowCount(nTableLimit);
    1731             :         }
    1732             : 
    1733         885 :         if (oResult->RowCount() > 0)
    1734             :         {
    1735         776 :             bRet = TRUE;
    1736             : 
    1737        1552 :             m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
    1738         776 :                 sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
    1739             : 
    1740        1552 :             std::map<std::string, int> oMapTableRefCount;
    1741        3713 :             for (int i = 0; i < oResult->RowCount(); i++)
    1742             :             {
    1743        2937 :                 const char *pszTableName = oResult->GetValue(0, i);
    1744        2937 :                 if (pszTableName == nullptr)
    1745           0 :                     continue;
    1746        2937 :                 if (++oMapTableRefCount[pszTableName] == 2)
    1747             :                 {
    1748             :                     // This should normally not happen if all constraints are
    1749             :                     // properly set
    1750           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1751             :                              "Table %s appearing several times in "
    1752             :                              "gpkg_contents and/or gpkg_geometry_columns",
    1753             :                              pszTableName);
    1754             :                 }
    1755             :             }
    1756             : 
    1757        1552 :             std::set<std::string> oExistingLayers;
    1758        3713 :             for (int i = 0; i < oResult->RowCount(); i++)
    1759             :             {
    1760        2937 :                 const char *pszTableName = oResult->GetValue(0, i);
    1761        2937 :                 if (pszTableName == nullptr)
    1762           2 :                     continue;
    1763             :                 const bool bTableHasSeveralGeomColumns =
    1764        2937 :                     oMapTableRefCount[pszTableName] > 1;
    1765        2937 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1766        2937 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1767        2937 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1768        2937 :                 const char *pszZ = oResult->GetValue(5, i);
    1769        2937 :                 const char *pszM = oResult->GetValue(6, i);
    1770             :                 bool bIsInGpkgContents =
    1771        2937 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1772        2937 :                 if (!bIsInGpkgContents)
    1773          41 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1774        2937 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1775        2937 :                 if (pszObjectType == nullptr ||
    1776        2936 :                     !(EQUAL(pszObjectType, "table") ||
    1777          21 :                       EQUAL(pszObjectType, "view")))
    1778             :                 {
    1779           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1780             :                              "Table/view %s is referenced in gpkg_contents, "
    1781             :                              "but does not exist",
    1782             :                              pszTableName);
    1783           1 :                     continue;
    1784             :                 }
    1785             :                 // Non-standard and undocumented behavior:
    1786             :                 // if the same table appears to have several geometry columns,
    1787             :                 // handle it for now as multiple layers named
    1788             :                 // "table_name (geom_col_name)"
    1789             :                 // The way we handle that might change in the future (e.g
    1790             :                 // could be a single layer with multiple geometry columns)
    1791             :                 const std::string osLayerNameWithGeomColName =
    1792        5453 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1793             :                                          pszGeomColName + ')'
    1794        5872 :                                    : std::string(pszTableName);
    1795        2936 :                 if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
    1796           1 :                     continue;
    1797        2935 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1798             :                 const std::string osLayerName = bTableHasSeveralGeomColumns
    1799             :                                                     ? osLayerNameWithGeomColName
    1800        2935 :                                                     : std::string(pszTableName);
    1801             :                 OGRGeoPackageTableLayer *poLayer =
    1802        2935 :                     new OGRGeoPackageTableLayer(this, osLayerName.c_str());
    1803        2935 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1804        2935 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1805        2935 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1806             :                 {
    1807         550 :                     if (pszZ && atoi(pszZ) == 2)
    1808           7 :                         bHasZ = false;
    1809         550 :                     if (pszM && atoi(pszM) == 2)
    1810           6 :                         bHasM = false;
    1811             :                 }
    1812        2935 :                 poLayer->SetOpeningParameters(
    1813             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1814             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1815        2935 :                 m_papoLayers[m_nLayers++] = poLayer;
    1816             :             }
    1817             :         }
    1818             :     }
    1819             : 
    1820        1069 :     bool bHasTileMatrixSet = false;
    1821        1069 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1822             :     {
    1823         517 :         bHasTileMatrixSet = SQLGetInteger(hDB,
    1824             :                                           "SELECT 1 FROM sqlite_master WHERE "
    1825             :                                           "name = 'gpkg_tile_matrix_set' AND "
    1826             :                                           "type IN ('table', 'view')",
    1827             :                                           nullptr) == 1;
    1828             :     }
    1829        1069 :     if (bHasTileMatrixSet)
    1830             :     {
    1831             :         std::string osSQL =
    1832             :             "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
    1833             :             "c.min_x, c.min_y, c.max_x, c.max_y, "
    1834             :             "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
    1835             :             "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
    1836             :             "c.table_name = tms.table_name WHERE "
    1837         516 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1838         516 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1839             :             osSubdatasetTableName =
    1840           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1841         516 :         if (!osSubdatasetTableName.empty())
    1842             :         {
    1843          14 :             char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
    1844             :                                            osSubdatasetTableName.c_str());
    1845          14 :             osSQL += pszTmp;
    1846          14 :             sqlite3_free(pszTmp);
    1847          14 :             SetPhysicalFilename(osFilename.c_str());
    1848             :         }
    1849         516 :         const int nTableLimit = GetOGRTableLimit();
    1850         516 :         if (nTableLimit > 0)
    1851             :         {
    1852         516 :             osSQL += " LIMIT ";
    1853         516 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1854             :         }
    1855             : 
    1856         516 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1857         516 :         if (!oResult)
    1858             :         {
    1859           0 :             return FALSE;
    1860             :         }
    1861             : 
    1862         516 :         if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
    1863             :         {
    1864           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1865             :                      "Cannot find table '%s' in GeoPackage dataset",
    1866             :                      osSubdatasetTableName.c_str());
    1867             :         }
    1868         515 :         else if (oResult->RowCount() == 1)
    1869             :         {
    1870         263 :             const char *pszTableName = oResult->GetValue(0, 0);
    1871         263 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1872         263 :             const char *pszDescription = oResult->GetValue(2, 0);
    1873         263 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1874         263 :             const char *pszMinX = oResult->GetValue(4, 0);
    1875         263 :             const char *pszMinY = oResult->GetValue(5, 0);
    1876         263 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1877         263 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1878         263 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1879         263 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1880         263 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1881         263 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1882         263 :             const char *pszDataType = oResult->GetValue(12, 0);
    1883         263 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1884             :                 pszTMSMaxY)
    1885             :             {
    1886         526 :                 bRet = OpenRaster(
    1887             :                     pszTableName, pszIdentifier, pszDescription,
    1888         263 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1889             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1890             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1891         263 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1892             :             }
    1893             :         }
    1894         252 :         else if (oResult->RowCount() >= 1)
    1895             :         {
    1896           4 :             bRet = TRUE;
    1897             : 
    1898           4 :             if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1899             :             {
    1900           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1901             :                          "File has more than %d raster tables. "
    1902             :                          "Limiting to first %d (can be overridden with "
    1903             :                          "OGR_TABLE_LIMIT config option)",
    1904             :                          nTableLimit, nTableLimit);
    1905           1 :                 oResult->LimitRowCount(nTableLimit);
    1906             :             }
    1907             : 
    1908           4 :             int nSDSCount = 0;
    1909        2010 :             for (int i = 0; i < oResult->RowCount(); i++)
    1910             :             {
    1911        2006 :                 const char *pszTableName = oResult->GetValue(0, i);
    1912        2006 :                 const char *pszIdentifier = oResult->GetValue(1, i);
    1913        2006 :                 if (pszTableName == nullptr)
    1914           0 :                     continue;
    1915             :                 m_aosSubDatasets.AddNameValue(
    1916             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
    1917        2006 :                     CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
    1918             :                 m_aosSubDatasets.AddNameValue(
    1919             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
    1920             :                     pszIdentifier
    1921        2006 :                         ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
    1922        4012 :                         : pszTableName);
    1923        2006 :                 nSDSCount++;
    1924             :             }
    1925             :         }
    1926             :     }
    1927             : 
    1928        1069 :     if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
    1929             :     {
    1930          30 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
    1931             :         {
    1932          20 :             bRet = TRUE;
    1933             :         }
    1934             :         else
    1935             :         {
    1936          10 :             CPLDebug("GPKG",
    1937             :                      "This GeoPackage has no vector content and is opened "
    1938             :                      "in read-only mode. If you open it in update mode, "
    1939             :                      "opening will be successful.");
    1940             :         }
    1941             :     }
    1942             : 
    1943        1069 :     if (eAccess == GA_Update)
    1944             :     {
    1945         206 :         FixupWrongRTreeTrigger();
    1946         206 :         FixupWrongMedataReferenceColumnNameUpdate();
    1947             :     }
    1948             : 
    1949        1069 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1950             : 
    1951        1069 :     return bRet;
    1952             : }
    1953             : 
    1954             : /************************************************************************/
    1955             : /*                    DetectSpatialRefSysColumns()                      */
    1956             : /************************************************************************/
    1957             : 
    1958        1076 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1959             : {
    1960             :     // Detect definition_12_063 column
    1961             :     {
    1962        1076 :         sqlite3_stmt *hSQLStmt = nullptr;
    1963        1076 :         int rc = sqlite3_prepare_v2(
    1964             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1965             :             &hSQLStmt, nullptr);
    1966        1076 :         if (rc == SQLITE_OK)
    1967             :         {
    1968          80 :             m_bHasDefinition12_063 = true;
    1969          80 :             sqlite3_finalize(hSQLStmt);
    1970             :         }
    1971             :     }
    1972             : 
    1973             :     // Detect epoch column
    1974        1076 :     if (m_bHasDefinition12_063)
    1975             :     {
    1976          80 :         sqlite3_stmt *hSQLStmt = nullptr;
    1977             :         int rc =
    1978          80 :             sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
    1979             :                                -1, &hSQLStmt, nullptr);
    1980          80 :         if (rc == SQLITE_OK)
    1981             :         {
    1982           5 :             m_bHasEpochColumn = true;
    1983           5 :             sqlite3_finalize(hSQLStmt);
    1984             :         }
    1985             :     }
    1986        1076 : }
    1987             : 
    1988             : /************************************************************************/
    1989             : /*                    FixupWrongRTreeTrigger()                          */
    1990             : /************************************************************************/
    1991             : 
    1992         206 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    1993             : {
    1994             :     auto oResult = SQLQuery(
    1995             :         hDB,
    1996             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    1997         206 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    1998         206 :     if (oResult == nullptr)
    1999           0 :         return;
    2000         206 :     if (oResult->RowCount() > 0)
    2001             :     {
    2002           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    2003             :     }
    2004         208 :     for (int i = 0; i < oResult->RowCount(); i++)
    2005             :     {
    2006           2 :         const char *pszName = oResult->GetValue(0, i);
    2007           2 :         const char *pszSQL = oResult->GetValue(1, i);
    2008           2 :         const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
    2009           2 :         if (pszPtr1)
    2010             :         {
    2011           2 :             const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
    2012             :             // Skipping over geometry column name
    2013           4 :             while (*pszPtr == ' ')
    2014           2 :                 pszPtr++;
    2015           2 :             if (pszPtr[0] == '"' || pszPtr[0] == '\'')
    2016             :             {
    2017           1 :                 char chStringDelim = pszPtr[0];
    2018           1 :                 pszPtr++;
    2019           9 :                 while (*pszPtr != '\0' && *pszPtr != chStringDelim)
    2020             :                 {
    2021           8 :                     if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
    2022           0 :                         pszPtr += 2;
    2023             :                     else
    2024           8 :                         pszPtr += 1;
    2025             :                 }
    2026           1 :                 if (*pszPtr == chStringDelim)
    2027           1 :                     pszPtr++;
    2028             :             }
    2029             :             else
    2030             :             {
    2031           1 :                 pszPtr++;
    2032           8 :                 while (*pszPtr != ' ')
    2033           7 :                     pszPtr++;
    2034             :             }
    2035           2 :             if (*pszPtr == ' ')
    2036             :             {
    2037           2 :                 SQLCommand(hDB,
    2038           4 :                            ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
    2039             :                                .c_str());
    2040           4 :                 CPLString newSQL;
    2041           2 :                 newSQL.assign(pszSQL, pszPtr1 - pszSQL);
    2042           2 :                 newSQL += " AFTER UPDATE";
    2043           2 :                 newSQL += pszPtr;
    2044           2 :                 SQLCommand(hDB, newSQL);
    2045             :             }
    2046             :         }
    2047             :     }
    2048             : }
    2049             : 
    2050             : /************************************************************************/
    2051             : /*             FixupWrongMedataReferenceColumnNameUpdate()              */
    2052             : /************************************************************************/
    2053             : 
    2054         206 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
    2055             : {
    2056             :     // Fix wrong trigger that was generated by GDAL < 2.4.0
    2057             :     // See https://github.com/qgis/QGIS/issues/42768
    2058             :     auto oResult = SQLQuery(
    2059             :         hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
    2060             :              "NAME ='gpkg_metadata_reference_column_name_update' AND "
    2061         206 :              "sql LIKE '%column_nameIS%'");
    2062         206 :     if (oResult == nullptr)
    2063           0 :         return;
    2064         206 :     if (oResult->RowCount() == 1)
    2065             :     {
    2066           1 :         CPLDebug("GPKG", "Fixing incorrect trigger "
    2067             :                          "gpkg_metadata_reference_column_name_update");
    2068           1 :         const char *pszSQL = oResult->GetValue(0, 0);
    2069             :         std::string osNewSQL(
    2070           3 :             CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
    2071             : 
    2072           1 :         SQLCommand(hDB,
    2073             :                    "DROP TRIGGER gpkg_metadata_reference_column_name_update");
    2074           1 :         SQLCommand(hDB, osNewSQL.c_str());
    2075             :     }
    2076             : }
    2077             : 
    2078             : /************************************************************************/
    2079             : /*                  ClearCachedRelationships()                          */
    2080             : /************************************************************************/
    2081             : 
    2082          35 : void GDALGeoPackageDataset::ClearCachedRelationships()
    2083             : {
    2084          35 :     m_bHasPopulatedRelationships = false;
    2085          35 :     m_osMapRelationships.clear();
    2086          35 : }
    2087             : 
    2088             : /************************************************************************/
    2089             : /*                           LoadRelationships()                        */
    2090             : /************************************************************************/
    2091             : 
    2092          49 : void GDALGeoPackageDataset::LoadRelationships() const
    2093             : {
    2094          49 :     m_osMapRelationships.clear();
    2095             : 
    2096          49 :     std::vector<std::string> oExcludedTables;
    2097          49 :     if (HasGpkgextRelationsTable())
    2098             :     {
    2099          30 :         LoadRelationshipsUsingRelatedTablesExtension();
    2100             : 
    2101          76 :         for (const auto &oRelationship : m_osMapRelationships)
    2102             :         {
    2103             :             oExcludedTables.emplace_back(
    2104          46 :                 oRelationship.second->GetMappingTableName());
    2105             :         }
    2106             :     }
    2107             : 
    2108             :     // Also load relationships defined using foreign keys (i.e. one-to-many
    2109             :     // relationships). Here we must exclude any relationships defined from the
    2110             :     // related tables extension, we don't want them included twice.
    2111          49 :     LoadRelationshipsFromForeignKeys(oExcludedTables);
    2112          49 :     m_bHasPopulatedRelationships = true;
    2113          49 : }
    2114             : 
    2115             : /************************************************************************/
    2116             : /*         LoadRelationshipsUsingRelatedTablesExtension()               */
    2117             : /************************************************************************/
    2118             : 
    2119          30 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
    2120             : {
    2121          30 :     m_osMapRelationships.clear();
    2122             : 
    2123             :     auto oResultTable = SQLQuery(
    2124          30 :         hDB, "SELECT base_table_name, base_primary_column, "
    2125             :              "related_table_name, related_primary_column, relation_name, "
    2126          60 :              "mapping_table_name FROM gpkgext_relations");
    2127          30 :     if (oResultTable && oResultTable->RowCount() > 0)
    2128             :     {
    2129          74 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    2130             :         {
    2131          47 :             const char *pszBaseTableName = oResultTable->GetValue(0, i);
    2132          47 :             if (!pszBaseTableName)
    2133             :             {
    2134           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2135             :                          "Could not retrieve base_table_name from "
    2136             :                          "gpkgext_relations");
    2137           1 :                 continue;
    2138             :             }
    2139          47 :             const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
    2140          47 :             if (!pszBasePrimaryColumn)
    2141             :             {
    2142           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2143             :                          "Could not retrieve base_primary_column from "
    2144             :                          "gpkgext_relations");
    2145           0 :                 continue;
    2146             :             }
    2147          47 :             const char *pszRelatedTableName = oResultTable->GetValue(2, i);
    2148          47 :             if (!pszRelatedTableName)
    2149             :             {
    2150           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2151             :                          "Could not retrieve related_table_name from "
    2152             :                          "gpkgext_relations");
    2153           0 :                 continue;
    2154             :             }
    2155          47 :             const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
    2156          47 :             if (!pszRelatedPrimaryColumn)
    2157             :             {
    2158           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2159             :                          "Could not retrieve related_primary_column from "
    2160             :                          "gpkgext_relations");
    2161           0 :                 continue;
    2162             :             }
    2163          47 :             const char *pszRelationName = oResultTable->GetValue(4, i);
    2164          47 :             if (!pszRelationName)
    2165             :             {
    2166           0 :                 CPLError(
    2167             :                     CE_Warning, CPLE_AppDefined,
    2168             :                     "Could not retrieve relation_name from gpkgext_relations");
    2169           0 :                 continue;
    2170             :             }
    2171          47 :             const char *pszMappingTableName = oResultTable->GetValue(5, i);
    2172          47 :             if (!pszMappingTableName)
    2173             :             {
    2174           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2175             :                          "Could not retrieve mapping_table_name from "
    2176             :                          "gpkgext_relations");
    2177           0 :                 continue;
    2178             :             }
    2179             : 
    2180             :             // confirm that mapping table exists
    2181             :             char *pszSQL =
    2182          47 :                 sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
    2183             :                                 "name='%q' AND type IN ('table', 'view')",
    2184             :                                 pszMappingTableName);
    2185          47 :             const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
    2186          47 :             sqlite3_free(pszSQL);
    2187             : 
    2188          47 :             if (nMappingTableCount < 1)
    2189             :             {
    2190           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2191             :                          "Relationship mapping table %s does not exist",
    2192             :                          pszMappingTableName);
    2193           1 :                 continue;
    2194             :             }
    2195             : 
    2196             :             const std::string osRelationName = GenerateNameForRelationship(
    2197          92 :                 pszBaseTableName, pszRelatedTableName, pszRelationName);
    2198             : 
    2199          92 :             std::string osType{};
    2200             :             // defined requirement classes -- for these types the relation name
    2201             :             // will be specific string value from the related tables extension.
    2202             :             // In this case we need to construct a unique relationship name
    2203             :             // based on the related tables
    2204          46 :             if (EQUAL(pszRelationName, "media") ||
    2205          34 :                 EQUAL(pszRelationName, "simple_attributes") ||
    2206          34 :                 EQUAL(pszRelationName, "features") ||
    2207          12 :                 EQUAL(pszRelationName, "attributes") ||
    2208           2 :                 EQUAL(pszRelationName, "tiles"))
    2209             :             {
    2210          44 :                 osType = pszRelationName;
    2211             :             }
    2212             :             else
    2213             :             {
    2214             :                 // user defined types default to features
    2215           2 :                 osType = "features";
    2216             :             }
    2217             : 
    2218             :             std::unique_ptr<GDALRelationship> poRelationship(
    2219             :                 new GDALRelationship(osRelationName, pszBaseTableName,
    2220         138 :                                      pszRelatedTableName, GRC_MANY_TO_MANY));
    2221             : 
    2222          92 :             poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
    2223          92 :             poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
    2224          92 :             poRelationship->SetLeftMappingTableFields({"base_id"});
    2225          92 :             poRelationship->SetRightMappingTableFields({"related_id"});
    2226          46 :             poRelationship->SetMappingTableName(pszMappingTableName);
    2227          46 :             poRelationship->SetRelatedTableType(osType);
    2228             : 
    2229          46 :             m_osMapRelationships[osRelationName] = std::move(poRelationship);
    2230             :         }
    2231             :     }
    2232          30 : }
    2233             : 
    2234             : /************************************************************************/
    2235             : /*                GenerateNameForRelationship()                         */
    2236             : /************************************************************************/
    2237             : 
    2238          66 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
    2239             :     const char *pszBaseTableName, const char *pszRelatedTableName,
    2240             :     const char *pszType)
    2241             : {
    2242             :     // defined requirement classes -- for these types the relation name will be
    2243             :     // specific string value from the related tables extension. In this case we
    2244             :     // need to construct a unique relationship name based on the related tables
    2245          66 :     if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
    2246          43 :         EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
    2247           8 :         EQUAL(pszType, "tiles"))
    2248             :     {
    2249         116 :         std::ostringstream stream;
    2250             :         stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
    2251          58 :                << pszType;
    2252          58 :         return stream.str();
    2253             :     }
    2254             :     else
    2255             :     {
    2256             :         // user defined types default to features
    2257           8 :         return pszType;
    2258             :     }
    2259             : }
    2260             : 
    2261             : /************************************************************************/
    2262             : /*                       ValidateRelationship()                         */
    2263             : /************************************************************************/
    2264             : 
    2265          24 : bool GDALGeoPackageDataset::ValidateRelationship(
    2266             :     const GDALRelationship *poRelationship, std::string &failureReason)
    2267             : {
    2268             : 
    2269          24 :     if (poRelationship->GetCardinality() !=
    2270             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2271             :     {
    2272           3 :         failureReason = "Only many to many relationships are supported";
    2273           3 :         return false;
    2274             :     }
    2275             : 
    2276          42 :     std::string osRelatedTableType = poRelationship->GetRelatedTableType();
    2277          53 :     if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
    2278          22 :         osRelatedTableType != "media" &&
    2279          12 :         osRelatedTableType != "simple_attributes" &&
    2280          43 :         osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
    2281             :     {
    2282             :         failureReason =
    2283           4 :             ("Related table type " + osRelatedTableType +
    2284             :              " is not a valid value for the GeoPackage specification. "
    2285             :              "Valid values are: features, media, simple_attributes, "
    2286             :              "attributes, tiles.")
    2287           2 :                 .c_str();
    2288           2 :         return false;
    2289             :     }
    2290             : 
    2291          19 :     const std::string &osLeftTableName = poRelationship->GetLeftTableName();
    2292          19 :     OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2293          19 :         GetLayerByName(osLeftTableName.c_str()));
    2294          19 :     if (!poLeftTable)
    2295             :     {
    2296           2 :         failureReason = ("Left table " + osLeftTableName +
    2297             :                          " is not an existing layer in the dataset")
    2298           1 :                             .c_str();
    2299           1 :         return false;
    2300             :     }
    2301          18 :     const std::string &osRightTableName = poRelationship->GetRightTableName();
    2302          18 :     OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2303          18 :         GetLayerByName(osRightTableName.c_str()));
    2304          18 :     if (!poRightTable)
    2305             :     {
    2306           2 :         failureReason = ("Right table " + osRightTableName +
    2307             :                          " is not an existing layer in the dataset")
    2308           1 :                             .c_str();
    2309           1 :         return false;
    2310             :     }
    2311             : 
    2312          17 :     const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
    2313          17 :     if (aosLeftTableFields.empty())
    2314             :     {
    2315           1 :         failureReason = "No left table fields were specified";
    2316           1 :         return false;
    2317             :     }
    2318          16 :     else if (aosLeftTableFields.size() > 1)
    2319             :     {
    2320             :         failureReason = "Only a single left table field is permitted for the "
    2321           1 :                         "GeoPackage specification";
    2322           1 :         return false;
    2323             :     }
    2324             :     else
    2325             :     {
    2326             :         // validate left field exists
    2327          30 :         if (poLeftTable->GetLayerDefn()->GetFieldIndex(
    2328          33 :                 aosLeftTableFields[0].c_str()) < 0 &&
    2329           3 :             !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
    2330             :         {
    2331           2 :             failureReason = ("Left table field " + aosLeftTableFields[0] +
    2332           2 :                              " does not exist in " + osLeftTableName)
    2333           1 :                                 .c_str();
    2334           1 :             return false;
    2335             :         }
    2336             :     }
    2337             : 
    2338          14 :     const auto &aosRightTableFields = poRelationship->GetRightTableFields();
    2339          14 :     if (aosRightTableFields.empty())
    2340             :     {
    2341           1 :         failureReason = "No right table fields were specified";
    2342           1 :         return false;
    2343             :     }
    2344          13 :     else if (aosRightTableFields.size() > 1)
    2345             :     {
    2346             :         failureReason = "Only a single right table field is permitted for the "
    2347           1 :                         "GeoPackage specification";
    2348           1 :         return false;
    2349             :     }
    2350             :     else
    2351             :     {
    2352             :         // validate right field exists
    2353          24 :         if (poRightTable->GetLayerDefn()->GetFieldIndex(
    2354          28 :                 aosRightTableFields[0].c_str()) < 0 &&
    2355           4 :             !EQUAL(poRightTable->GetFIDColumn(),
    2356             :                    aosRightTableFields[0].c_str()))
    2357             :         {
    2358           4 :             failureReason = ("Right table field " + aosRightTableFields[0] +
    2359           4 :                              " does not exist in " + osRightTableName)
    2360           2 :                                 .c_str();
    2361           2 :             return false;
    2362             :         }
    2363             :     }
    2364             : 
    2365          10 :     return true;
    2366             : }
    2367             : 
    2368             : /************************************************************************/
    2369             : /*                         InitRaster()                                 */
    2370             : /************************************************************************/
    2371             : 
    2372         347 : bool GDALGeoPackageDataset::InitRaster(
    2373             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
    2374             :     double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2375             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2376             :     const char *pszContentsMaxY, char **papszOpenOptionsIn,
    2377             :     const SQLResult &oResult, int nIdxInResult)
    2378             : {
    2379         347 :     m_osRasterTable = pszTableName;
    2380         347 :     m_dfTMSMinX = dfMinX;
    2381         347 :     m_dfTMSMaxY = dfMaxY;
    2382             : 
    2383             :     // Despite prior checking, the type might be Binary and
    2384             :     // SQLResultGetValue() not working properly on it
    2385         347 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2386         347 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2387             :     {
    2388           0 :         return false;
    2389             :     }
    2390         347 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2391         347 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2392         347 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2393             :     {
    2394           0 :         return false;
    2395             :     }
    2396         347 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2397         347 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2398         347 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2399             :         nTileHeight > 65536)
    2400             :     {
    2401           0 :         return false;
    2402             :     }
    2403             :     int nTileMatrixWidth = static_cast<int>(
    2404         694 :         std::min(static_cast<GIntBig>(INT_MAX),
    2405         347 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2406             :     int nTileMatrixHeight = static_cast<int>(
    2407         694 :         std::min(static_cast<GIntBig>(INT_MAX),
    2408         347 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2409         347 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2410             :     {
    2411           0 :         return false;
    2412             :     }
    2413             : 
    2414             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2415         347 :     double dfGDALMinX = dfMinX;
    2416         347 :     double dfGDALMinY = dfMinY;
    2417         347 :     double dfGDALMaxX = dfMaxX;
    2418         347 :     double dfGDALMaxY = dfMaxY;
    2419             :     pszContentsMinX =
    2420         347 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2421             :     pszContentsMinY =
    2422         347 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2423             :     pszContentsMaxX =
    2424         347 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2425             :     pszContentsMaxY =
    2426         347 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2427         347 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2428         347 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2429             :     {
    2430         693 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2431         346 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2432             :         {
    2433         346 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2434         346 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2435         346 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2436         346 :             dfGDALMaxY = CPLAtof(pszContentsMaxY);
    2437             :         }
    2438             :         else
    2439             :         {
    2440           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2441             :                      "Illegal min_x/min_y/max_x/max_y values for %s in open "
    2442             :                      "options and/or gpkg_contents. Using bounds of "
    2443             :                      "gpkg_tile_matrix_set instead",
    2444             :                      pszTableName);
    2445             :         }
    2446             :     }
    2447         347 :     if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
    2448             :     {
    2449           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2450             :                  "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
    2451           0 :         return false;
    2452             :     }
    2453             : 
    2454         347 :     int nBandCount = 0;
    2455             :     const char *pszBAND_COUNT =
    2456         347 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2457         347 :     if (poParentDS)
    2458             :     {
    2459          86 :         nBandCount = poParentDS->GetRasterCount();
    2460             :     }
    2461         261 :     else if (m_eDT != GDT_Byte)
    2462             :     {
    2463          65 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
    2464           0 :             !EQUAL(pszBAND_COUNT, "1"))
    2465             :         {
    2466           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2467             :                      "BAND_COUNT ignored for non-Byte data");
    2468             :         }
    2469          65 :         nBandCount = 1;
    2470             :     }
    2471             :     else
    2472             :     {
    2473         196 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
    2474             :         {
    2475          69 :             nBandCount = atoi(pszBAND_COUNT);
    2476          69 :             if (nBandCount == 1)
    2477           5 :                 GetMetadata("IMAGE_STRUCTURE");
    2478             :         }
    2479             :         else
    2480             :         {
    2481         127 :             GetMetadata("IMAGE_STRUCTURE");
    2482         127 :             nBandCount = m_nBandCountFromMetadata;
    2483         127 :             if (nBandCount == 1)
    2484          31 :                 m_eTF = GPKG_TF_PNG;
    2485             :         }
    2486         196 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2487             :         {
    2488           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2489             :         }
    2490         196 :         if (nBandCount <= 0 || nBandCount > 4)
    2491          82 :             nBandCount = 4;
    2492             :     }
    2493             : 
    2494         347 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2495             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2496             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2497         347 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2498             : }
    2499             : 
    2500             : /************************************************************************/
    2501             : /*                      ComputeTileAndPixelShifts()                     */
    2502             : /************************************************************************/
    2503             : 
    2504         749 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2505             : {
    2506             :     int nTileWidth, nTileHeight;
    2507         749 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2508             : 
    2509             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2510         749 :     const double dfShiftXPixels =
    2511         749 :         (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
    2512         749 :     if (dfShiftXPixels / nTileWidth <= INT_MIN ||
    2513         747 :         dfShiftXPixels / nTileWidth > INT_MAX)
    2514           2 :         return false;
    2515         747 :     const int64_t nShiftXPixels =
    2516         747 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2517         747 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2518         747 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2519          11 :         m_nShiftXTiles--;
    2520         747 :     m_nShiftXPixelsMod =
    2521         747 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2522             :         nTileWidth;
    2523             : 
    2524         747 :     const double dfShiftYPixels =
    2525         747 :         (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
    2526         747 :     if (dfShiftYPixels / nTileHeight <= INT_MIN ||
    2527         747 :         dfShiftYPixels / nTileHeight > INT_MAX)
    2528           1 :         return false;
    2529         746 :     const int64_t nShiftYPixels =
    2530         746 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2531         746 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2532         746 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2533          11 :         m_nShiftYTiles--;
    2534         746 :     m_nShiftYPixelsMod =
    2535         746 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2536             :         nTileHeight;
    2537         746 :     return true;
    2538             : }
    2539             : 
    2540             : /************************************************************************/
    2541             : /*                            AllocCachedTiles()                        */
    2542             : /************************************************************************/
    2543             : 
    2544         746 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2545             : {
    2546             :     int nTileWidth, nTileHeight;
    2547         746 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2548             : 
    2549             :     // We currently need 4 caches because of
    2550             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2551         746 :     const int nCacheCount = 4;
    2552             :     /*
    2553             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2554             :             (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
    2555             :     */
    2556         746 :     m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
    2557             :         cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
    2558             :                           m_nDTSize),
    2559             :         nTileWidth, nTileHeight));
    2560         746 :     if (m_pabyCachedTiles == nullptr)
    2561             :     {
    2562           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
    2563             :                  nTileWidth, nTileHeight);
    2564           0 :         return false;
    2565             :     }
    2566             : 
    2567         746 :     return true;
    2568             : }
    2569             : 
    2570             : /************************************************************************/
    2571             : /*                         InitRaster()                                 */
    2572             : /************************************************************************/
    2573             : 
    2574         586 : bool GDALGeoPackageDataset::InitRaster(
    2575             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
    2576             :     int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
    2577             :     double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
    2578             :     int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
    2579             :     double dfGDALMaxX, double dfGDALMaxY)
    2580             : {
    2581         586 :     m_osRasterTable = pszTableName;
    2582         586 :     m_dfTMSMinX = dfTMSMinX;
    2583         586 :     m_dfTMSMaxY = dfTMSMaxY;
    2584         586 :     m_nZoomLevel = nZoomLevel;
    2585         586 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2586         586 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2587             : 
    2588         586 :     m_bGeoTransformValid = true;
    2589         586 :     m_adfGeoTransform[0] = dfGDALMinX;
    2590         586 :     m_adfGeoTransform[1] = dfPixelXSize;
    2591         586 :     m_adfGeoTransform[3] = dfGDALMaxY;
    2592         586 :     m_adfGeoTransform[5] = -dfPixelYSize;
    2593         586 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2594         586 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2595         586 :     if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2596             :     {
    2597           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
    2598             :                  dfRasterXSize, dfRasterYSize);
    2599           0 :         return false;
    2600             :     }
    2601         586 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2602         586 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2603             : 
    2604         586 :     if (poParentDS)
    2605             :     {
    2606         325 :         m_poParentDS = poParentDS;
    2607         325 :         eAccess = poParentDS->eAccess;
    2608         325 :         hDB = poParentDS->hDB;
    2609         325 :         m_eTF = poParentDS->m_eTF;
    2610         325 :         m_eDT = poParentDS->m_eDT;
    2611         325 :         m_nDTSize = poParentDS->m_nDTSize;
    2612         325 :         m_dfScale = poParentDS->m_dfScale;
    2613         325 :         m_dfOffset = poParentDS->m_dfOffset;
    2614         325 :         m_dfPrecision = poParentDS->m_dfPrecision;
    2615         325 :         m_usGPKGNull = poParentDS->m_usGPKGNull;
    2616         325 :         m_nQuality = poParentDS->m_nQuality;
    2617         325 :         m_nZLevel = poParentDS->m_nZLevel;
    2618         325 :         m_bDither = poParentDS->m_bDither;
    2619             :         /*m_nSRID = poParentDS->m_nSRID;*/
    2620         325 :         m_osWHERE = poParentDS->m_osWHERE;
    2621         325 :         SetDescription(CPLSPrintf("%s - zoom_level=%d",
    2622         325 :                                   poParentDS->GetDescription(), m_nZoomLevel));
    2623             :     }
    2624             : 
    2625        2060 :     for (int i = 1; i <= nBandCount; i++)
    2626             :     {
    2627             :         GDALGeoPackageRasterBand *poNewBand =
    2628        1474 :             new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
    2629        1474 :         if (poParentDS)
    2630             :         {
    2631         761 :             int bHasNoData = FALSE;
    2632             :             double dfNoDataValue =
    2633         761 :                 poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    2634         761 :             if (bHasNoData)
    2635          24 :                 poNewBand->SetNoDataValueInternal(dfNoDataValue);
    2636             :         }
    2637        1474 :         SetBand(i, poNewBand);
    2638             : 
    2639        1474 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2640             :         {
    2641           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2642             :         }
    2643        1474 :         if (!m_osNodataValueFromMetadata.empty())
    2644             :         {
    2645           8 :             poNewBand->SetNoDataValueInternal(
    2646             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2647             :         }
    2648             :     }
    2649             : 
    2650         586 :     if (!ComputeTileAndPixelShifts())
    2651             :     {
    2652           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2653             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2654           3 :         return false;
    2655             :     }
    2656             : 
    2657         583 :     GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2658         583 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2659             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2660             : 
    2661         583 :     return AllocCachedTiles();
    2662             : }
    2663             : 
    2664             : /************************************************************************/
    2665             : /*                 GDALGPKGMBTilesGetTileFormat()                       */
    2666             : /************************************************************************/
    2667             : 
    2668          80 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
    2669             : {
    2670          80 :     GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
    2671          80 :     if (pszTF)
    2672             :     {
    2673          80 :         if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
    2674           1 :             eTF = GPKG_TF_PNG_JPEG;
    2675          79 :         else if (EQUAL(pszTF, "PNG"))
    2676          46 :             eTF = GPKG_TF_PNG;
    2677          33 :         else if (EQUAL(pszTF, "PNG8"))
    2678           6 :             eTF = GPKG_TF_PNG8;
    2679          27 :         else if (EQUAL(pszTF, "JPEG"))
    2680          14 :             eTF = GPKG_TF_JPEG;
    2681          13 :         else if (EQUAL(pszTF, "WEBP"))
    2682          13 :             eTF = GPKG_TF_WEBP;
    2683             :         else
    2684             :         {
    2685           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2686             :                      "Unsuppoted value for TILE_FORMAT: %s", pszTF);
    2687             :         }
    2688             :     }
    2689          80 :     return eTF;
    2690             : }
    2691             : 
    2692          28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
    2693             : {
    2694          28 :     switch (eTF)
    2695             :     {
    2696          26 :         case GPKG_TF_PNG:
    2697             :         case GPKG_TF_PNG8:
    2698          26 :             return "png";
    2699           1 :         case GPKG_TF_JPEG:
    2700           1 :             return "jpg";
    2701           1 :         case GPKG_TF_WEBP:
    2702           1 :             return "webp";
    2703           0 :         default:
    2704           0 :             break;
    2705             :     }
    2706           0 :     CPLError(CE_Failure, CPLE_NotSupported,
    2707             :              "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
    2708           0 :     return nullptr;
    2709             : }
    2710             : 
    2711             : /************************************************************************/
    2712             : /*                         OpenRaster()                                 */
    2713             : /************************************************************************/
    2714             : 
    2715         263 : bool GDALGeoPackageDataset::OpenRaster(
    2716             :     const char *pszTableName, const char *pszIdentifier,
    2717             :     const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
    2718             :     double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2719             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2720             :     const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
    2721             : {
    2722         263 :     if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
    2723           0 :         return false;
    2724             : 
    2725             :     // Config option just for debug, and for example force set to NaN
    2726             :     // which is not supported
    2727         526 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2728         526 :     CPLString osUom;
    2729         526 :     CPLString osFieldName;
    2730         526 :     CPLString osGridCellEncoding;
    2731         263 :     if (!bIsTiles)
    2732             :     {
    2733          65 :         char *pszSQL = sqlite3_mprintf(
    2734             :             "SELECT datatype, scale, offset, data_null, precision FROM "
    2735             :             "gpkg_2d_gridded_coverage_ancillary "
    2736             :             "WHERE tile_matrix_set_name = '%q' "
    2737             :             "AND datatype IN ('integer', 'float')"
    2738             :             "AND (scale > 0 OR scale IS NULL)",
    2739             :             pszTableName);
    2740          65 :         auto oResult = SQLQuery(hDB, pszSQL);
    2741          65 :         sqlite3_free(pszSQL);
    2742          65 :         if (!oResult || oResult->RowCount() == 0)
    2743             :         {
    2744           0 :             return false;
    2745             :         }
    2746          65 :         const char *pszDataType = oResult->GetValue(0, 0);
    2747          65 :         const char *pszScale = oResult->GetValue(1, 0);
    2748          65 :         const char *pszOffset = oResult->GetValue(2, 0);
    2749          65 :         const char *pszDataNull = oResult->GetValue(3, 0);
    2750          65 :         const char *pszPrecision = oResult->GetValue(4, 0);
    2751          65 :         if (pszDataNull)
    2752          23 :             osDataNull = pszDataNull;
    2753          65 :         if (EQUAL(pszDataType, "float"))
    2754             :         {
    2755           6 :             SetDataType(GDT_Float32);
    2756           6 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    2757             :         }
    2758             :         else
    2759             :         {
    2760          59 :             SetDataType(GDT_Float32);
    2761          59 :             m_eTF = GPKG_TF_PNG_16BIT;
    2762          59 :             const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
    2763          59 :             const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
    2764          59 :             if (dfScale == 1.0)
    2765             :             {
    2766          59 :                 if (dfOffset == 0.0)
    2767             :                 {
    2768          24 :                     SetDataType(GDT_UInt16);
    2769             :                 }
    2770          35 :                 else if (dfOffset == -32768.0)
    2771             :                 {
    2772          35 :                     SetDataType(GDT_Int16);
    2773             :                 }
    2774             :                 // coverity[tainted_data]
    2775           0 :                 else if (dfOffset == -32767.0 && !osDataNull.empty() &&
    2776           0 :                          CPLAtof(osDataNull) == 65535.0)
    2777             :                 // Given that we will map the nodata value to -32768
    2778             :                 {
    2779           0 :                     SetDataType(GDT_Int16);
    2780             :                 }
    2781             :             }
    2782             : 
    2783             :             // Check that the tile offset and scales are compatible of a
    2784             :             // final integer result.
    2785          59 :             if (m_eDT != GDT_Float32)
    2786             :             {
    2787             :                 // coverity[tainted_data]
    2788          59 :                 if (dfScale == 1.0 && dfOffset == -32768.0 &&
    2789         118 :                     !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
    2790             :                 {
    2791             :                     // Given that we will map the nodata value to -32768
    2792           9 :                     pszSQL = sqlite3_mprintf(
    2793             :                         "SELECT 1 FROM "
    2794             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2795             :                         "tpudt_name = '%q' "
    2796             :                         "AND NOT ((offset = 0.0 or offset = 1.0) "
    2797             :                         "AND scale = 1.0) "
    2798             :                         "LIMIT 1",
    2799             :                         pszTableName);
    2800             :                 }
    2801             :                 else
    2802             :                 {
    2803          50 :                     pszSQL = sqlite3_mprintf(
    2804             :                         "SELECT 1 FROM "
    2805             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2806             :                         "tpudt_name = '%q' "
    2807             :                         "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
    2808             :                         pszTableName);
    2809             :                 }
    2810          59 :                 sqlite3_stmt *hSQLStmt = nullptr;
    2811             :                 int rc =
    2812          59 :                     sqlite3_prepare_v2(hDB, pszSQL, -1, &hSQLStmt, nullptr);
    2813             : 
    2814          59 :                 if (rc == SQLITE_OK)
    2815             :                 {
    2816          59 :                     if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
    2817             :                     {
    2818           8 :                         SetDataType(GDT_Float32);
    2819             :                     }
    2820          59 :                     sqlite3_finalize(hSQLStmt);
    2821             :                 }
    2822             :                 else
    2823             :                 {
    2824           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2825             :                              "Error when running %s", pszSQL);
    2826             :                 }
    2827          59 :                 sqlite3_free(pszSQL);
    2828             :             }
    2829             : 
    2830          59 :             SetGlobalOffsetScale(dfOffset, dfScale);
    2831             :         }
    2832          65 :         if (pszPrecision)
    2833          65 :             m_dfPrecision = CPLAtof(pszPrecision);
    2834             : 
    2835             :         // Request those columns in a separate query, so as to keep
    2836             :         // compatibility with pre OGC 17-066r1 databases
    2837             :         pszSQL =
    2838          65 :             sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
    2839             :                             "gpkg_2d_gridded_coverage_ancillary "
    2840             :                             "WHERE tile_matrix_set_name = '%q'",
    2841             :                             pszTableName);
    2842          65 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    2843          65 :         oResult = SQLQuery(hDB, pszSQL);
    2844          65 :         CPLPopErrorHandler();
    2845          65 :         sqlite3_free(pszSQL);
    2846          65 :         if (oResult && oResult->RowCount() == 1)
    2847             :         {
    2848          64 :             const char *pszUom = oResult->GetValue(0, 0);
    2849          64 :             if (pszUom)
    2850           2 :                 osUom = pszUom;
    2851          64 :             const char *pszFieldName = oResult->GetValue(1, 0);
    2852          64 :             if (pszFieldName)
    2853          64 :                 osFieldName = pszFieldName;
    2854          64 :             const char *pszGridCellEncoding = oResult->GetValue(2, 0);
    2855          64 :             if (pszGridCellEncoding)
    2856          64 :                 osGridCellEncoding = pszGridCellEncoding;
    2857             :         }
    2858             :     }
    2859             : 
    2860         263 :     m_bRecordInsertedInGPKGContent = true;
    2861         263 :     m_nSRID = nSRSId;
    2862             : 
    2863         525 :     if (auto poSRS = GetSpatialRef(nSRSId))
    2864             :     {
    2865         262 :         m_oSRS = *(poSRS.get());
    2866             :     }
    2867             : 
    2868             :     /* Various sanity checks added in the SELECT */
    2869         263 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2870         526 :     CPLString osQuotedTableName(pszQuotedTableName);
    2871         263 :     sqlite3_free(pszQuotedTableName);
    2872         263 :     char *pszSQL = sqlite3_mprintf(
    2873             :         "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
    2874             :         "tile_height, matrix_width, matrix_height "
    2875             :         "FROM gpkg_tile_matrix tm "
    2876             :         "WHERE table_name = %s "
    2877             :         // INT_MAX would be the theoretical maximum value to avoid
    2878             :         // overflows, but that's already a insane value.
    2879             :         "AND zoom_level >= 0 AND zoom_level <= 65536 "
    2880             :         "AND pixel_x_size > 0 AND pixel_y_size > 0 "
    2881             :         "AND tile_width >= 1 AND tile_width <= 65536 "
    2882             :         "AND tile_height >= 1 AND tile_height <= 65536 "
    2883             :         "AND matrix_width >= 1 AND matrix_height >= 1",
    2884             :         osQuotedTableName.c_str());
    2885         526 :     CPLString osSQL(pszSQL);
    2886             :     const char *pszZoomLevel =
    2887         263 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2888         263 :     if (pszZoomLevel)
    2889             :     {
    2890           5 :         if (GetUpdate())
    2891           1 :             osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
    2892             :         else
    2893             :         {
    2894             :             osSQL += CPLSPrintf(
    2895             :                 " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
    2896             :                 "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
    2897             :                 atoi(pszZoomLevel), atoi(pszZoomLevel),
    2898           4 :                 osQuotedTableName.c_str());
    2899             :         }
    2900             :     }
    2901             :     // In read-only mode, only lists non empty zoom levels
    2902         258 :     else if (!GetUpdate())
    2903             :     {
    2904             :         osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
    2905             :                             "tm.zoom_level LIMIT 1)",
    2906         207 :                             osQuotedTableName.c_str());
    2907             :     }
    2908             :     else  // if( pszZoomLevel == nullptr )
    2909             :     {
    2910             :         osSQL +=
    2911             :             CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
    2912          51 :                        osQuotedTableName.c_str());
    2913             :     }
    2914         263 :     osSQL += " ORDER BY zoom_level DESC";
    2915             :     // To avoid denial of service.
    2916         263 :     osSQL += " LIMIT 100";
    2917             : 
    2918         526 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2919         263 :     if (!oResult || oResult->RowCount() == 0)
    2920             :     {
    2921         106 :         if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
    2922         106 :             pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
    2923             :             pszContentsMaxY != nullptr)
    2924             :         {
    2925          52 :             osSQL = pszSQL;
    2926          52 :             osSQL += " ORDER BY zoom_level DESC";
    2927          52 :             if (!GetUpdate())
    2928          26 :                 osSQL += " LIMIT 1";
    2929          52 :             oResult = SQLQuery(hDB, osSQL.c_str());
    2930             :         }
    2931          53 :         if (!oResult || oResult->RowCount() == 0)
    2932             :         {
    2933           1 :             if (oResult && pszZoomLevel != nullptr)
    2934             :             {
    2935           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2936             :                          "ZOOM_LEVEL is probably not valid w.r.t tile "
    2937             :                          "table content");
    2938             :             }
    2939           1 :             sqlite3_free(pszSQL);
    2940           1 :             return false;
    2941             :         }
    2942             :     }
    2943         262 :     sqlite3_free(pszSQL);
    2944             : 
    2945             :     // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
    2946             :     // actually exist.
    2947             : 
    2948             :     // CAUTION: Do not move those variables inside inner scope !
    2949         524 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2950             : 
    2951         262 :     if (CPLTestBool(
    2952             :             CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
    2953             :     {
    2954          13 :         pszSQL = sqlite3_mprintf(
    2955             :             "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
    2956             :             "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
    2957             :             pszTableName, atoi(oResult->GetValue(0, 0)));
    2958          13 :         auto oResult2 = SQLQuery(hDB, pszSQL);
    2959          13 :         sqlite3_free(pszSQL);
    2960          26 :         if (!oResult2 || oResult2->RowCount() == 0 ||
    2961             :             // Can happen if table is empty
    2962          38 :             oResult2->GetValue(0, 0) == nullptr ||
    2963             :             // Can happen if table has no NOT NULL constraint on tile_row
    2964             :             // and that all tile_row are NULL
    2965          12 :             oResult2->GetValue(1, 0) == nullptr)
    2966             :         {
    2967           1 :             return false;
    2968             :         }
    2969          12 :         const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
    2970          12 :         const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
    2971          12 :         const int nTileWidth = atoi(oResult->GetValue(3, 0));
    2972          12 :         const int nTileHeight = atoi(oResult->GetValue(4, 0));
    2973             :         osContentsMinX =
    2974          24 :             CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2975          12 :                                              atoi(oResult2->GetValue(0, 0)));
    2976             :         osContentsMaxY =
    2977          24 :             CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2978          12 :                                              atoi(oResult2->GetValue(1, 0)));
    2979             :         osContentsMaxX = CPLSPrintf(
    2980          24 :             "%.17g", dfMinX + dfPixelXSize * nTileWidth *
    2981          12 :                                   (1 + atoi(oResult2->GetValue(2, 0))));
    2982             :         osContentsMinY = CPLSPrintf(
    2983          24 :             "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
    2984          12 :                                   (1 + atoi(oResult2->GetValue(3, 0))));
    2985          12 :         pszContentsMinX = osContentsMinX.c_str();
    2986          12 :         pszContentsMinY = osContentsMinY.c_str();
    2987          12 :         pszContentsMaxX = osContentsMaxX.c_str();
    2988          12 :         pszContentsMaxY = osContentsMaxY.c_str();
    2989             :     }
    2990             : 
    2991         261 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    2992             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    2993         261 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    2994             :     {
    2995           3 :         return false;
    2996             :     }
    2997             : 
    2998             :     auto poBand =
    2999         258 :         reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    3000         258 :     if (!osDataNull.empty())
    3001             :     {
    3002          23 :         double dfGPKGNoDataValue = CPLAtof(osDataNull);
    3003          23 :         if (m_eTF == GPKG_TF_PNG_16BIT)
    3004             :         {
    3005          21 :             if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
    3006          21 :                 static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
    3007             :             {
    3008           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3009             :                          "data_null = %.17g is invalid for integer data_type",
    3010             :                          dfGPKGNoDataValue);
    3011             :             }
    3012             :             else
    3013             :             {
    3014          21 :                 m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
    3015          21 :                 if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
    3016           9 :                     dfGPKGNoDataValue = -32768.0;
    3017          12 :                 else if (m_eDT == GDT_Float32)
    3018             :                 {
    3019             :                     // Pick a value that is unlikely to be hit with offset &
    3020             :                     // scale
    3021           4 :                     dfGPKGNoDataValue = -std::numeric_limits<float>::max();
    3022             :                 }
    3023          21 :                 poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
    3024             :             }
    3025             :         }
    3026             :         else
    3027             :         {
    3028           2 :             poBand->SetNoDataValueInternal(
    3029           2 :                 static_cast<float>(dfGPKGNoDataValue));
    3030             :         }
    3031             :     }
    3032         258 :     if (!osUom.empty())
    3033             :     {
    3034           2 :         poBand->SetUnitTypeInternal(osUom);
    3035             :     }
    3036         258 :     if (!osFieldName.empty())
    3037             :     {
    3038          64 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    3039             :     }
    3040         258 :     if (!osGridCellEncoding.empty())
    3041             :     {
    3042          64 :         if (osGridCellEncoding == "grid-value-is-center")
    3043             :         {
    3044          15 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3045             :                                             GDALMD_AOP_POINT);
    3046             :         }
    3047          49 :         else if (osGridCellEncoding == "grid-value-is-area")
    3048             :         {
    3049          45 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3050             :                                             GDALMD_AOP_AREA);
    3051             :         }
    3052             :         else
    3053             :         {
    3054           4 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3055             :                                             GDALMD_AOP_POINT);
    3056           4 :             GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
    3057             :                 "GRID_CELL_ENCODING", osGridCellEncoding);
    3058             :         }
    3059             :     }
    3060             : 
    3061         258 :     CheckUnknownExtensions(true);
    3062             : 
    3063             :     // Do this after CheckUnknownExtensions() so that m_eTF is set to
    3064             :     // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
    3065         258 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    3066         258 :     if (pszTF)
    3067             :     {
    3068           4 :         if (!GetUpdate())
    3069             :         {
    3070           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3071             :                      "TILE_FORMAT open option ignored in read-only mode");
    3072             :         }
    3073           4 :         else if (m_eTF == GPKG_TF_PNG_16BIT ||
    3074           4 :                  m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3075             :         {
    3076           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3077             :                      "TILE_FORMAT open option ignored on gridded coverages");
    3078             :         }
    3079             :         else
    3080             :         {
    3081           4 :             GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    3082           4 :             if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
    3083             :             {
    3084           1 :                 if (!RegisterWebPExtension())
    3085           0 :                     return false;
    3086             :             }
    3087           4 :             m_eTF = eTF;
    3088             :         }
    3089             :     }
    3090             : 
    3091         258 :     ParseCompressionOptions(papszOpenOptionsIn);
    3092             : 
    3093         258 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    3094             : 
    3095             :     // Set metadata
    3096         258 :     if (pszIdentifier && pszIdentifier[0])
    3097         258 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    3098         258 :     if (pszDescription && pszDescription[0])
    3099          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    3100             : 
    3101             :     // Add overviews
    3102         343 :     for (int i = 1; i < oResult->RowCount(); i++)
    3103             :     {
    3104          86 :         GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3105          86 :         poOvrDS->ShareLockWithParentDataset(this);
    3106          86 :         if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
    3107             :                                  dfMaxY, pszContentsMinX, pszContentsMinY,
    3108             :                                  pszContentsMaxX, pszContentsMaxY,
    3109          86 :                                  papszOpenOptionsIn, *oResult, i))
    3110             :         {
    3111           0 :             delete poOvrDS;
    3112           1 :             break;
    3113             :         }
    3114             : 
    3115          86 :         m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3116         172 :             CPLRealloc(m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
    3117          86 :                                              (m_nOverviewCount + 1)));
    3118          86 :         m_papoOverviewDS[m_nOverviewCount++] = poOvrDS;
    3119             : 
    3120             :         int nTileWidth, nTileHeight;
    3121          86 :         poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3122          87 :         if (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
    3123           1 :             poOvrDS->GetRasterYSize() < nTileHeight)
    3124             :         {
    3125           1 :             break;
    3126             :         }
    3127             :     }
    3128             : 
    3129         258 :     return true;
    3130             : }
    3131             : 
    3132             : /************************************************************************/
    3133             : /*                           GetSpatialRef()                            */
    3134             : /************************************************************************/
    3135             : 
    3136          14 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
    3137             : {
    3138          14 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3139             : }
    3140             : 
    3141             : /************************************************************************/
    3142             : /*                           SetSpatialRef()                            */
    3143             : /************************************************************************/
    3144             : 
    3145         138 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3146             : {
    3147         138 :     if (nBands == 0)
    3148             :     {
    3149           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3150             :                  "SetProjection() not supported on a dataset with 0 band");
    3151           1 :         return CE_Failure;
    3152             :     }
    3153         137 :     if (eAccess != GA_Update)
    3154             :     {
    3155           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3156             :                  "SetProjection() not supported on read-only dataset");
    3157           1 :         return CE_Failure;
    3158             :     }
    3159             : 
    3160         136 :     const int nSRID = GetSrsId(poSRS);
    3161         272 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3162         136 :     if (poTS && nSRID != poTS->nEPSGCode)
    3163             :     {
    3164           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3165             :                  "Projection should be EPSG:%d for %s tiling scheme",
    3166           1 :                  poTS->nEPSGCode, m_osTilingScheme.c_str());
    3167           1 :         return CE_Failure;
    3168             :     }
    3169             : 
    3170         135 :     m_nSRID = nSRID;
    3171         135 :     m_oSRS.Clear();
    3172         135 :     if (poSRS)
    3173         134 :         m_oSRS = *poSRS;
    3174             : 
    3175         135 :     if (m_bRecordInsertedInGPKGContent)
    3176             :     {
    3177         110 :         char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
    3178             :                                        "WHERE lower(table_name) = lower('%q')",
    3179             :                                        m_nSRID, m_osRasterTable.c_str());
    3180         110 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3181         110 :         sqlite3_free(pszSQL);
    3182         110 :         if (eErr != OGRERR_NONE)
    3183           0 :             return CE_Failure;
    3184             : 
    3185         110 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
    3186             :                                  "WHERE lower(table_name) = lower('%q')",
    3187             :                                  m_nSRID, m_osRasterTable.c_str());
    3188         110 :         eErr = SQLCommand(hDB, pszSQL);
    3189         110 :         sqlite3_free(pszSQL);
    3190         110 :         if (eErr != OGRERR_NONE)
    3191           0 :             return CE_Failure;
    3192             :     }
    3193             : 
    3194         135 :     return CE_None;
    3195             : }
    3196             : 
    3197             : /************************************************************************/
    3198             : /*                          GetGeoTransform()                           */
    3199             : /************************************************************************/
    3200             : 
    3201          33 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
    3202             : {
    3203          33 :     memcpy(padfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
    3204          33 :     if (!m_bGeoTransformValid)
    3205           2 :         return CE_Failure;
    3206             :     else
    3207          31 :         return CE_None;
    3208             : }
    3209             : 
    3210             : /************************************************************************/
    3211             : /*                          SetGeoTransform()                           */
    3212             : /************************************************************************/
    3213             : 
    3214         168 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
    3215             : {
    3216         168 :     if (nBands == 0)
    3217             :     {
    3218           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3219             :                  "SetGeoTransform() not supported on a dataset with 0 band");
    3220           2 :         return CE_Failure;
    3221             :     }
    3222         166 :     if (eAccess != GA_Update)
    3223             :     {
    3224           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3225             :                  "SetGeoTransform() not supported on read-only dataset");
    3226           1 :         return CE_Failure;
    3227             :     }
    3228         165 :     if (m_bGeoTransformValid)
    3229             :     {
    3230           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3231             :                  "Cannot modify geotransform once set");
    3232           1 :         return CE_Failure;
    3233             :     }
    3234         164 :     if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
    3235         164 :         padfGeoTransform[5] > 0.0)
    3236             :     {
    3237           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3238             :                  "Only north-up non rotated geotransform supported");
    3239           0 :         return CE_Failure;
    3240             :     }
    3241             : 
    3242         164 :     if (m_nZoomLevel < 0)
    3243             :     {
    3244         163 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3245         163 :         if (poTS)
    3246             :         {
    3247          20 :             double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3248          20 :             double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3249         199 :             for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
    3250         179 :                  m_nZoomLevel++)
    3251             :             {
    3252         198 :                 double dfExpectedPixelXSize =
    3253         198 :                     dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
    3254         198 :                 double dfExpectedPixelYSize =
    3255         198 :                     dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
    3256         198 :                 if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
    3257         198 :                         1e-8 * dfExpectedPixelXSize &&
    3258          19 :                     fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
    3259          19 :                         1e-8 * dfExpectedPixelYSize)
    3260             :                 {
    3261          19 :                     break;
    3262             :                 }
    3263             :             }
    3264          20 :             if (m_nZoomLevel == MAX_ZOOM_LEVEL)
    3265             :             {
    3266           1 :                 m_nZoomLevel = -1;
    3267           1 :                 CPLError(
    3268             :                     CE_Failure, CPLE_NotSupported,
    3269             :                     "Could not find an appropriate zoom level of %s tiling "
    3270             :                     "scheme that matches raster pixel size",
    3271             :                     m_osTilingScheme.c_str());
    3272           1 :                 return CE_Failure;
    3273             :             }
    3274             :         }
    3275             :     }
    3276             : 
    3277         163 :     memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
    3278         163 :     m_bGeoTransformValid = true;
    3279             : 
    3280         163 :     return FinalizeRasterRegistration();
    3281             : }
    3282             : 
    3283             : /************************************************************************/
    3284             : /*                      FinalizeRasterRegistration()                    */
    3285             : /************************************************************************/
    3286             : 
    3287         163 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3288             : {
    3289             :     OGRErr eErr;
    3290             : 
    3291         163 :     m_dfTMSMinX = m_adfGeoTransform[0];
    3292         163 :     m_dfTMSMaxY = m_adfGeoTransform[3];
    3293             : 
    3294             :     int nTileWidth, nTileHeight;
    3295         163 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3296             : 
    3297         163 :     if (m_nZoomLevel < 0)
    3298             :     {
    3299         143 :         m_nZoomLevel = 0;
    3300         217 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3301         143 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3302          74 :             m_nZoomLevel++;
    3303             :     }
    3304             : 
    3305         163 :     double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
    3306         163 :     double dfPixelYSizeZoomLevel0 =
    3307         163 :         fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
    3308             :     int nTileXCountZoomLevel0 =
    3309         163 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3310             :     int nTileYCountZoomLevel0 =
    3311         163 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3312             : 
    3313         326 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3314         163 :     if (poTS)
    3315             :     {
    3316          20 :         CPLAssert(m_nZoomLevel >= 0);
    3317          20 :         m_dfTMSMinX = poTS->dfMinX;
    3318          20 :         m_dfTMSMaxY = poTS->dfMaxY;
    3319          20 :         dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3320          20 :         dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3321          20 :         nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
    3322          20 :         nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
    3323             :     }
    3324         163 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3325         163 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3326             : 
    3327         163 :     if (!ComputeTileAndPixelShifts())
    3328             :     {
    3329           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3330             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3331           0 :         return CE_Failure;
    3332             :     }
    3333             : 
    3334         163 :     if (!AllocCachedTiles())
    3335             :     {
    3336           0 :         return CE_Failure;
    3337             :     }
    3338             : 
    3339         163 :     double dfGDALMinX = m_adfGeoTransform[0];
    3340         163 :     double dfGDALMinY =
    3341         163 :         m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3342         163 :     double dfGDALMaxX =
    3343         163 :         m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3344         163 :     double dfGDALMaxY = m_adfGeoTransform[3];
    3345             : 
    3346         163 :     if (SoftStartTransaction() != OGRERR_NONE)
    3347           0 :         return CE_Failure;
    3348             : 
    3349             :     const char *pszCurrentDate =
    3350         163 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3351             :     CPLString osInsertGpkgContentsFormatting(
    3352             :         "INSERT INTO gpkg_contents "
    3353             :         "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
    3354             :         "last_change,srs_id) VALUES "
    3355         326 :         "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
    3356         163 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3357         163 :     osInsertGpkgContentsFormatting += ",%d)";
    3358         326 :     char *pszSQL = sqlite3_mprintf(
    3359             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3360         163 :         (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
    3361             :         m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
    3362             :         dfGDALMaxX, dfGDALMaxY,
    3363             :         pszCurrentDate ? pszCurrentDate
    3364             :                        : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
    3365             :         m_nSRID);
    3366             : 
    3367         163 :     eErr = SQLCommand(hDB, pszSQL);
    3368         163 :     sqlite3_free(pszSQL);
    3369         163 :     if (eErr != OGRERR_NONE)
    3370             :     {
    3371           0 :         SoftRollbackTransaction();
    3372           0 :         return CE_Failure;
    3373             :     }
    3374             : 
    3375         163 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3376             :                                          dfPixelXSizeZoomLevel0;
    3377         163 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3378             :                                          dfPixelYSizeZoomLevel0;
    3379             : 
    3380             :     pszSQL =
    3381         163 :         sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
    3382             :                         "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
    3383             :                         "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
    3384             :                         m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
    3385             :                         dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
    3386         163 :     eErr = SQLCommand(hDB, pszSQL);
    3387         163 :     sqlite3_free(pszSQL);
    3388         163 :     if (eErr != OGRERR_NONE)
    3389             :     {
    3390           0 :         SoftRollbackTransaction();
    3391           0 :         return CE_Failure;
    3392             :     }
    3393             : 
    3394         163 :     m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3395         163 :         CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
    3396             : 
    3397         561 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3398             :     {
    3399         398 :         double dfPixelXSizeZoomLevel = 0.0;
    3400         398 :         double dfPixelYSizeZoomLevel = 0.0;
    3401         398 :         int nTileMatrixWidth = 0;
    3402         398 :         int nTileMatrixHeight = 0;
    3403         398 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3404             :         {
    3405         217 :             dfPixelXSizeZoomLevel =
    3406         217 :                 m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
    3407         217 :             dfPixelYSizeZoomLevel =
    3408         217 :                 fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
    3409             :         }
    3410             :         else
    3411             :         {
    3412         181 :             dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
    3413         181 :             dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
    3414             :         }
    3415         398 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3416         398 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3417             : 
    3418         398 :         pszSQL = sqlite3_mprintf(
    3419             :             "INSERT INTO gpkg_tile_matrix "
    3420             :             "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
    3421             :             "height,pixel_x_size,pixel_y_size) VALUES "
    3422             :             "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3423             :             m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
    3424             :             nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
    3425             :             dfPixelYSizeZoomLevel);
    3426         398 :         eErr = SQLCommand(hDB, pszSQL);
    3427         398 :         sqlite3_free(pszSQL);
    3428         398 :         if (eErr != OGRERR_NONE)
    3429             :         {
    3430           0 :             SoftRollbackTransaction();
    3431           0 :             return CE_Failure;
    3432             :         }
    3433             : 
    3434         398 :         if (i < m_nZoomLevel)
    3435             :         {
    3436         235 :             GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3437         235 :             poOvrDS->ShareLockWithParentDataset(this);
    3438         235 :             poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
    3439             :                                 m_dfTMSMaxY, dfPixelXSizeZoomLevel,
    3440             :                                 dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
    3441             :                                 nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
    3442             :                                 dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    3443             : 
    3444         235 :             m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
    3445             :         }
    3446             :     }
    3447             : 
    3448         163 :     if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
    3449             :     {
    3450          40 :         eErr = SQLCommand(
    3451             :             hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
    3452          40 :         m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
    3453          40 :         if (eErr != OGRERR_NONE)
    3454             :         {
    3455           0 :             SoftRollbackTransaction();
    3456           0 :             return CE_Failure;
    3457             :         }
    3458             :     }
    3459             : 
    3460         163 :     SoftCommitTransaction();
    3461             : 
    3462         163 :     m_nOverviewCount = m_nZoomLevel;
    3463         163 :     m_bRecordInsertedInGPKGContent = true;
    3464             : 
    3465         163 :     return CE_None;
    3466             : }
    3467             : 
    3468             : /************************************************************************/
    3469             : /*                             FlushCache()                             */
    3470             : /************************************************************************/
    3471             : 
    3472        2307 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3473             : {
    3474        2307 :     if (m_bInFlushCache)
    3475           0 :         return CE_None;
    3476             : 
    3477        2307 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3478             :     {
    3479        2304 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3480             :     }
    3481             : 
    3482        2307 :     if (m_bRemoveOGREmptyTable)
    3483             :     {
    3484         556 :         m_bRemoveOGREmptyTable = false;
    3485         556 :         RemoveOGREmptyTable();
    3486             :     }
    3487             : 
    3488        2307 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3489             : 
    3490        2307 :     FlushMetadata();
    3491             : 
    3492        2307 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3493             :     {
    3494             :         // Needed again as above IFlushCacheWithErrCode()
    3495             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3496             :         // which modifies metadata
    3497        2307 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3498             :     }
    3499             : 
    3500        2307 :     return eErr;
    3501             : }
    3502             : 
    3503        4489 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3504             : 
    3505             : {
    3506        4489 :     if (m_bInFlushCache)
    3507        2115 :         return CE_None;
    3508        2374 :     m_bInFlushCache = true;
    3509        2374 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3510             :     {
    3511             :         // Clean-up metadata that will go to PAM by removing items that
    3512             :         // are reconstructed.
    3513        1752 :         CPLStringList aosMD;
    3514        1478 :         for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
    3515             :              ++papszIter)
    3516             :         {
    3517         602 :             char *pszKey = nullptr;
    3518         602 :             CPLParseNameValue(*papszIter, &pszKey);
    3519        1204 :             if (pszKey &&
    3520         602 :                 (EQUAL(pszKey, "AREA_OR_POINT") ||
    3521         461 :                  EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
    3522         248 :                  EQUAL(pszKey, "ZOOM_LEVEL") ||
    3523         632 :                  STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
    3524             :             {
    3525             :                 // remove it
    3526             :             }
    3527             :             else
    3528             :             {
    3529          30 :                 aosMD.AddString(*papszIter);
    3530             :             }
    3531         602 :             CPLFree(pszKey);
    3532             :         }
    3533         876 :         oMDMD.SetMetadata(aosMD.List());
    3534         876 :         oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
    3535             : 
    3536        1752 :         GDALPamDataset::FlushCache(bAtClosing);
    3537             :     }
    3538             :     else
    3539             :     {
    3540             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3541        1498 :         GDALDataset::FlushCache(bAtClosing);
    3542             :     }
    3543             : 
    3544        5997 :     for (int i = 0; i < m_nLayers; i++)
    3545             :     {
    3546        3623 :         m_papoLayers[i]->RunDeferredCreationIfNecessary();
    3547        3623 :         m_papoLayers[i]->CreateSpatialIndexIfNecessary();
    3548             :     }
    3549             : 
    3550             :     // Update raster table last_change column in gpkg_contents if needed
    3551        2374 :     if (m_bHasModifiedTiles)
    3552             :     {
    3553         496 :         for (int i = 1; i <= nBands; ++i)
    3554             :         {
    3555             :             auto poBand =
    3556         334 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3557         334 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3558             :             {
    3559         331 :                 poBand->InvalidateStatistics();
    3560         331 :                 if (psPam && psPam->pszPamFilename)
    3561         331 :                     VSIUnlink(psPam->pszPamFilename);
    3562             :             }
    3563             :         }
    3564             : 
    3565         162 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3566             : 
    3567         162 :         m_bHasModifiedTiles = false;
    3568             :     }
    3569             : 
    3570        2374 :     CPLErr eErr = FlushTiles();
    3571             : 
    3572        2374 :     m_bInFlushCache = false;
    3573        2374 :     return eErr;
    3574             : }
    3575             : 
    3576             : /************************************************************************/
    3577             : /*                       GetCurrentDateEscapedSQL()                      */
    3578             : /************************************************************************/
    3579             : 
    3580        1643 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3581             : {
    3582             :     const char *pszCurrentDate =
    3583        1643 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3584        1643 :     if (pszCurrentDate)
    3585           6 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3586        1640 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3587             : }
    3588             : 
    3589             : /************************************************************************/
    3590             : /*                    UpdateGpkgContentsLastChange()                    */
    3591             : /************************************************************************/
    3592             : 
    3593             : OGRErr
    3594         715 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3595             : {
    3596             :     char *pszSQL =
    3597         715 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3598             :                         "last_change = %s "
    3599             :                         "WHERE lower(table_name) = lower('%q')",
    3600        1430 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3601         715 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3602         715 :     sqlite3_free(pszSQL);
    3603         715 :     return eErr;
    3604             : }
    3605             : 
    3606             : /************************************************************************/
    3607             : /*                          IBuildOverviews()                           */
    3608             : /************************************************************************/
    3609             : 
    3610          20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
    3611             :     const char *pszResampling, int nOverviews, const int *panOverviewList,
    3612             :     int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
    3613             :     void *pProgressData, CSLConstList papszOptions)
    3614             : {
    3615          20 :     if (GetAccess() != GA_Update)
    3616             :     {
    3617           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3618             :                  "Overview building not supported on a database opened in "
    3619             :                  "read-only mode");
    3620           1 :         return CE_Failure;
    3621             :     }
    3622          19 :     if (m_poParentDS != nullptr)
    3623             :     {
    3624           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3625             :                  "Overview building not supported on overview dataset");
    3626           1 :         return CE_Failure;
    3627             :     }
    3628             : 
    3629          18 :     if (nOverviews == 0)
    3630             :     {
    3631           5 :         for (int i = 0; i < m_nOverviewCount; i++)
    3632           3 :             m_papoOverviewDS[i]->FlushCache(false);
    3633             : 
    3634           2 :         SoftStartTransaction();
    3635             : 
    3636           2 :         if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3637             :         {
    3638           1 :             char *pszSQL = sqlite3_mprintf(
    3639             :                 "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
    3640             :                 "(SELECT y.id FROM \"%w\" x "
    3641             :                 "JOIN gpkg_2d_gridded_tile_ancillary y "
    3642             :                 "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
    3643             :                 "x.zoom_level < %d)",
    3644             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
    3645           1 :             OGRErr eErr = SQLCommand(hDB, pszSQL);
    3646           1 :             sqlite3_free(pszSQL);
    3647           1 :             if (eErr != OGRERR_NONE)
    3648             :             {
    3649           0 :                 SoftRollbackTransaction();
    3650           0 :                 return CE_Failure;
    3651             :             }
    3652             :         }
    3653             : 
    3654             :         char *pszSQL =
    3655           2 :             sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
    3656             :                             m_osRasterTable.c_str(), m_nZoomLevel);
    3657           2 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3658           2 :         sqlite3_free(pszSQL);
    3659           2 :         if (eErr != OGRERR_NONE)
    3660             :         {
    3661           0 :             SoftRollbackTransaction();
    3662           0 :             return CE_Failure;
    3663             :         }
    3664             : 
    3665           2 :         SoftCommitTransaction();
    3666             : 
    3667           2 :         return CE_None;
    3668             :     }
    3669             : 
    3670          16 :     if (nBandsIn != nBands)
    3671             :     {
    3672           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3673             :                  "Generation of overviews in GPKG only"
    3674             :                  "supported when operating on all bands.");
    3675           0 :         return CE_Failure;
    3676             :     }
    3677             : 
    3678          16 :     if (m_nOverviewCount == 0)
    3679             :     {
    3680           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3681             :                  "Image too small to support overviews");
    3682           0 :         return CE_Failure;
    3683             :     }
    3684             : 
    3685          16 :     FlushCache(false);
    3686          60 :     for (int i = 0; i < nOverviews; i++)
    3687             :     {
    3688          47 :         if (panOverviewList[i] < 2)
    3689             :         {
    3690           1 :             CPLError(CE_Failure, CPLE_IllegalArg,
    3691             :                      "Overview factor must be >= 2");
    3692           1 :             return CE_Failure;
    3693             :         }
    3694             : 
    3695          46 :         bool bFound = false;
    3696          46 :         int jCandidate = -1;
    3697          46 :         int nMaxOvFactor = 0;
    3698         196 :         for (int j = 0; j < m_nOverviewCount; j++)
    3699             :         {
    3700         190 :             auto poODS = m_papoOverviewDS[j];
    3701         190 :             const int nOvFactor = static_cast<int>(
    3702         190 :                 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3703             : 
    3704         190 :             nMaxOvFactor = nOvFactor;
    3705             : 
    3706         190 :             if (nOvFactor == panOverviewList[i])
    3707             :             {
    3708          40 :                 bFound = true;
    3709          40 :                 break;
    3710             :             }
    3711             : 
    3712         150 :             if (jCandidate < 0 && nOvFactor > panOverviewList[i])
    3713           1 :                 jCandidate = j;
    3714             :         }
    3715             : 
    3716          46 :         if (!bFound)
    3717             :         {
    3718             :             /* Mostly for debug */
    3719           6 :             if (!CPLTestBool(CPLGetConfigOption(
    3720             :                     "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
    3721             :             {
    3722           2 :                 CPLString osOvrList;
    3723           4 :                 for (int j = 0; j < m_nOverviewCount; j++)
    3724             :                 {
    3725           2 :                     auto poODS = m_papoOverviewDS[j];
    3726           2 :                     const int nOvFactor =
    3727           2 :                         static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
    3728           2 :                                                    m_adfGeoTransform[1]);
    3729             : 
    3730           2 :                     if (j != 0)
    3731           0 :                         osOvrList += " ";
    3732           2 :                     osOvrList += CPLSPrintf("%d", nOvFactor);
    3733             :                 }
    3734           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3735             :                          "Only overviews %s can be computed",
    3736             :                          osOvrList.c_str());
    3737           2 :                 return CE_Failure;
    3738             :             }
    3739             :             else
    3740             :             {
    3741           4 :                 int nOvFactor = panOverviewList[i];
    3742           4 :                 if (jCandidate < 0)
    3743           3 :                     jCandidate = m_nOverviewCount;
    3744             : 
    3745           4 :                 int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
    3746           4 :                 int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
    3747           4 :                 if (!(jCandidate == m_nOverviewCount &&
    3748           3 :                       nOvFactor == 2 * nMaxOvFactor) &&
    3749           1 :                     !m_bZoomOther)
    3750             :                 {
    3751           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3752             :                              "Use of overview factor %d causes gpkg_zoom_other "
    3753             :                              "extension to be needed",
    3754             :                              nOvFactor);
    3755           1 :                     RegisterZoomOtherExtension();
    3756           1 :                     m_bZoomOther = true;
    3757             :                 }
    3758             : 
    3759           4 :                 SoftStartTransaction();
    3760             : 
    3761           4 :                 CPLAssert(jCandidate > 0);
    3762           4 :                 int nNewZoomLevel =
    3763           4 :                     m_papoOverviewDS[jCandidate - 1]->m_nZoomLevel;
    3764             : 
    3765             :                 char *pszSQL;
    3766             :                 OGRErr eErr;
    3767          24 :                 for (int k = 0; k <= jCandidate; k++)
    3768             :                 {
    3769          60 :                     pszSQL = sqlite3_mprintf(
    3770             :                         "UPDATE gpkg_tile_matrix SET zoom_level = %d "
    3771             :                         "WHERE lower(table_name) = lower('%q') AND zoom_level "
    3772             :                         "= %d",
    3773          20 :                         m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
    3774          20 :                         m_nZoomLevel - k);
    3775          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3776          20 :                     sqlite3_free(pszSQL);
    3777          20 :                     if (eErr != OGRERR_NONE)
    3778             :                     {
    3779           0 :                         SoftRollbackTransaction();
    3780           0 :                         return CE_Failure;
    3781             :                     }
    3782             : 
    3783             :                     pszSQL =
    3784          20 :                         sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
    3785             :                                         "WHERE zoom_level = %d",
    3786             :                                         m_osRasterTable.c_str(),
    3787          20 :                                         m_nZoomLevel - k + 1, m_nZoomLevel - k);
    3788          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3789          20 :                     sqlite3_free(pszSQL);
    3790          20 :                     if (eErr != OGRERR_NONE)
    3791             :                     {
    3792           0 :                         SoftRollbackTransaction();
    3793           0 :                         return CE_Failure;
    3794             :                     }
    3795             :                 }
    3796             : 
    3797           4 :                 double dfGDALMinX = m_adfGeoTransform[0];
    3798           4 :                 double dfGDALMinY =
    3799           4 :                     m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3800           4 :                 double dfGDALMaxX =
    3801           4 :                     m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3802           4 :                 double dfGDALMaxY = m_adfGeoTransform[3];
    3803           4 :                 double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
    3804           4 :                 double dfPixelYSizeZoomLevel =
    3805           4 :                     fabs(m_adfGeoTransform[5]) * nOvFactor;
    3806             :                 int nTileWidth, nTileHeight;
    3807           4 :                 GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3808           4 :                 int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
    3809           4 :                 int nTileMatrixHeight =
    3810           4 :                     (nOvYSize + nTileHeight - 1) / nTileHeight;
    3811           4 :                 pszSQL = sqlite3_mprintf(
    3812             :                     "INSERT INTO gpkg_tile_matrix "
    3813             :                     "(table_name,zoom_level,matrix_width,matrix_height,tile_"
    3814             :                     "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
    3815             :                     "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
    3816             :                     m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
    3817             :                     nTileMatrixHeight, nTileWidth, nTileHeight,
    3818             :                     dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
    3819           4 :                 eErr = SQLCommand(hDB, pszSQL);
    3820           4 :                 sqlite3_free(pszSQL);
    3821           4 :                 if (eErr != OGRERR_NONE)
    3822             :                 {
    3823           0 :                     SoftRollbackTransaction();
    3824           0 :                     return CE_Failure;
    3825             :                 }
    3826             : 
    3827           4 :                 SoftCommitTransaction();
    3828             : 
    3829           4 :                 m_nZoomLevel++; /* this change our zoom level as well as
    3830             :                                    previous overviews */
    3831          20 :                 for (int k = 0; k < jCandidate; k++)
    3832          16 :                     m_papoOverviewDS[k]->m_nZoomLevel++;
    3833             : 
    3834           4 :                 GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3835           4 :                 poOvrDS->ShareLockWithParentDataset(this);
    3836           4 :                 poOvrDS->InitRaster(
    3837             :                     this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
    3838             :                     m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
    3839             :                     nTileWidth, nTileHeight, nTileMatrixWidth,
    3840             :                     nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
    3841             :                     dfGDALMaxY);
    3842           4 :                 m_papoOverviewDS =
    3843           8 :                     static_cast<GDALGeoPackageDataset **>(CPLRealloc(
    3844           4 :                         m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
    3845           4 :                                               (m_nOverviewCount + 1)));
    3846             : 
    3847           4 :                 if (jCandidate < m_nOverviewCount)
    3848             :                 {
    3849           1 :                     memmove(m_papoOverviewDS + jCandidate + 1,
    3850           1 :                             m_papoOverviewDS + jCandidate,
    3851             :                             sizeof(GDALGeoPackageDataset *) *
    3852           1 :                                 (m_nOverviewCount - jCandidate));
    3853             :                 }
    3854           4 :                 m_papoOverviewDS[jCandidate] = poOvrDS;
    3855           4 :                 m_nOverviewCount++;
    3856             :             }
    3857             :         }
    3858             :     }
    3859             : 
    3860             :     GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
    3861          13 :         CPLCalloc(sizeof(GDALRasterBand **), nBands));
    3862          13 :     CPLErr eErr = CE_None;
    3863          49 :     for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
    3864             :     {
    3865          72 :         papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
    3866          36 :             CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
    3867          36 :         int iCurOverview = 0;
    3868         185 :         for (int i = 0; i < nOverviews; i++)
    3869             :         {
    3870         149 :             int j = 0;  // Used after for.
    3871         724 :             for (; j < m_nOverviewCount; j++)
    3872             :             {
    3873         724 :                 auto poODS = m_papoOverviewDS[j];
    3874         724 :                 const int nOvFactor = static_cast<int>(
    3875         724 :                     0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3876             : 
    3877         724 :                 if (nOvFactor == panOverviewList[i])
    3878             :                 {
    3879         298 :                     papapoOverviewBands[iBand][iCurOverview] =
    3880         149 :                         poODS->GetRasterBand(iBand + 1);
    3881         149 :                     iCurOverview++;
    3882         149 :                     break;
    3883             :                 }
    3884             :             }
    3885         149 :             if (j == m_nOverviewCount)
    3886             :             {
    3887           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3888             :                          "Could not find dataset corresponding to ov factor %d",
    3889           0 :                          panOverviewList[i]);
    3890           0 :                 eErr = CE_Failure;
    3891             :             }
    3892             :         }
    3893          36 :         if (eErr == CE_None)
    3894             :         {
    3895          36 :             CPLAssert(iCurOverview == nOverviews);
    3896             :         }
    3897             :     }
    3898             : 
    3899          13 :     if (eErr == CE_None)
    3900          13 :         eErr = GDALRegenerateOverviewsMultiBand(
    3901          13 :             nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
    3902             :             pfnProgress, pProgressData, papszOptions);
    3903             : 
    3904          49 :     for (int iBand = 0; iBand < nBands; iBand++)
    3905             :     {
    3906          36 :         CPLFree(papapoOverviewBands[iBand]);
    3907             :     }
    3908          13 :     CPLFree(papapoOverviewBands);
    3909             : 
    3910          13 :     return eErr;
    3911             : }
    3912             : 
    3913             : /************************************************************************/
    3914             : /*                            GetFileList()                             */
    3915             : /************************************************************************/
    3916             : 
    3917          36 : char **GDALGeoPackageDataset::GetFileList()
    3918             : {
    3919          36 :     TryLoadXML();
    3920          36 :     return GDALPamDataset::GetFileList();
    3921             : }
    3922             : 
    3923             : /************************************************************************/
    3924             : /*                      GetMetadataDomainList()                         */
    3925             : /************************************************************************/
    3926             : 
    3927          32 : char **GDALGeoPackageDataset::GetMetadataDomainList()
    3928             : {
    3929          32 :     GetMetadata();
    3930          32 :     if (!m_osRasterTable.empty())
    3931           5 :         GetMetadata("GEOPACKAGE");
    3932          32 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    3933          32 :                                    TRUE, "SUBDATASETS", nullptr);
    3934             : }
    3935             : 
    3936             : /************************************************************************/
    3937             : /*                        CheckMetadataDomain()                         */
    3938             : /************************************************************************/
    3939             : 
    3940        4603 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3941             : {
    3942        4768 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3943         165 :         m_osRasterTable.empty())
    3944             :     {
    3945           4 :         CPLError(
    3946             :             CE_Warning, CPLE_IllegalArg,
    3947             :             "Using GEOPACKAGE for a non-raster geopackage is not supported. "
    3948             :             "Using default domain instead");
    3949           4 :         return nullptr;
    3950             :     }
    3951        4599 :     return pszDomain;
    3952             : }
    3953             : 
    3954             : /************************************************************************/
    3955             : /*                           HasMetadataTables()                        */
    3956             : /************************************************************************/
    3957             : 
    3958        4544 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3959             : {
    3960        4544 :     if (m_nHasMetadataTables < 0)
    3961             :     {
    3962             :         const int nCount =
    3963        1746 :             SQLGetInteger(hDB,
    3964             :                           "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
    3965             :                           "('gpkg_metadata', 'gpkg_metadata_reference') "
    3966             :                           "AND type IN ('table', 'view')",
    3967             :                           nullptr);
    3968        1746 :         m_nHasMetadataTables = nCount == 2;
    3969             :     }
    3970        4544 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3971             : }
    3972             : 
    3973             : /************************************************************************/
    3974             : /*                         HasDataColumnsTable()                        */
    3975             : /************************************************************************/
    3976             : 
    3977         896 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3978             : {
    3979        1792 :     const int nCount = SQLGetInteger(
    3980         896 :         hDB,
    3981             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    3982             :         "AND type IN ('table', 'view')",
    3983             :         nullptr);
    3984         896 :     return nCount == 1;
    3985             : }
    3986             : 
    3987             : /************************************************************************/
    3988             : /*                    HasDataColumnConstraintsTable()                   */
    3989             : /************************************************************************/
    3990             : 
    3991         119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
    3992             : {
    3993         119 :     const int nCount = SQLGetInteger(hDB,
    3994             :                                      "SELECT 1 FROM sqlite_master WHERE name = "
    3995             :                                      "'gpkg_data_column_constraints'"
    3996             :                                      "AND type IN ('table', 'view')",
    3997             :                                      nullptr);
    3998         119 :     return nCount == 1;
    3999             : }
    4000             : 
    4001             : /************************************************************************/
    4002             : /*                  HasDataColumnConstraintsTableGPKG_1_0()             */
    4003             : /************************************************************************/
    4004             : 
    4005          73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
    4006             : {
    4007          73 :     if (m_nApplicationId != GP10_APPLICATION_ID)
    4008          71 :         return false;
    4009             :     // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
    4010             :     // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
    4011           2 :     bool bRet = false;
    4012           2 :     sqlite3_stmt *hSQLStmt = nullptr;
    4013           2 :     int rc = sqlite3_prepare_v2(hDB,
    4014             :                                 "SELECT minIsInclusive, maxIsInclusive FROM "
    4015             :                                 "gpkg_data_column_constraints",
    4016             :                                 -1, &hSQLStmt, nullptr);
    4017           2 :     if (rc == SQLITE_OK)
    4018             :     {
    4019           2 :         bRet = true;
    4020           2 :         sqlite3_finalize(hSQLStmt);
    4021             :     }
    4022           2 :     return bRet;
    4023             : }
    4024             : 
    4025             : /************************************************************************/
    4026             : /*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
    4027             : /************************************************************************/
    4028             : 
    4029          49 : bool GDALGeoPackageDataset::
    4030             :     CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
    4031             : {
    4032          49 :     if (!HasDataColumnsTable())
    4033             :     {
    4034             :         // Geopackage < 1.3 had
    4035             :         // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
    4036             :         // gpkg_contents(table_name) instead of the unique constraint.
    4037          10 :         if (OGRERR_NONE !=
    4038          10 :             SQLCommand(
    4039             :                 GetDB(),
    4040             :                 "CREATE TABLE gpkg_data_columns ("
    4041             :                 "table_name TEXT NOT NULL,"
    4042             :                 "column_name TEXT NOT NULL,"
    4043             :                 "name TEXT,"
    4044             :                 "title TEXT,"
    4045             :                 "description TEXT,"
    4046             :                 "mime_type TEXT,"
    4047             :                 "constraint_name TEXT,"
    4048             :                 "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
    4049             :                 "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
    4050             :         {
    4051           0 :             return false;
    4052             :         }
    4053             :     }
    4054          49 :     if (!HasDataColumnConstraintsTable())
    4055             :     {
    4056          22 :         const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    4057          11 :                                            ? "min_is_inclusive"
    4058             :                                            : "minIsInclusive";
    4059          22 :         const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    4060          11 :                                            ? "max_is_inclusive"
    4061             :                                            : "maxIsInclusive";
    4062             : 
    4063             :         const std::string osSQL(
    4064             :             CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
    4065             :                        "constraint_name TEXT NOT NULL,"
    4066             :                        "constraint_type TEXT NOT NULL,"
    4067             :                        "value TEXT,"
    4068             :                        "min NUMERIC,"
    4069             :                        "%s BOOLEAN,"
    4070             :                        "max NUMERIC,"
    4071             :                        "%s BOOLEAN,"
    4072             :                        "description TEXT,"
    4073             :                        "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
    4074             :                        "constraint_type, value));",
    4075          11 :                        min_is_inclusive, max_is_inclusive));
    4076          11 :         if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
    4077             :         {
    4078           0 :             return false;
    4079             :         }
    4080             :     }
    4081          49 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4082             :     {
    4083           0 :         return false;
    4084             :     }
    4085          49 :     if (SQLGetInteger(GetDB(),
    4086             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4087             :                       "table_name = 'gpkg_data_columns'",
    4088          49 :                       nullptr) != 1)
    4089             :     {
    4090          11 :         if (OGRERR_NONE !=
    4091          11 :             SQLCommand(
    4092             :                 GetDB(),
    4093             :                 "INSERT INTO gpkg_extensions "
    4094             :                 "(table_name,column_name,extension_name,definition,scope) "
    4095             :                 "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
    4096             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4097             :                 "'read-write')"))
    4098             :         {
    4099           0 :             return false;
    4100             :         }
    4101             :     }
    4102          49 :     if (SQLGetInteger(GetDB(),
    4103             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4104             :                       "table_name = 'gpkg_data_column_constraints'",
    4105          49 :                       nullptr) != 1)
    4106             :     {
    4107          11 :         if (OGRERR_NONE !=
    4108          11 :             SQLCommand(
    4109             :                 GetDB(),
    4110             :                 "INSERT INTO gpkg_extensions "
    4111             :                 "(table_name,column_name,extension_name,definition,scope) "
    4112             :                 "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
    4113             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4114             :                 "'read-write')"))
    4115             :         {
    4116           0 :             return false;
    4117             :         }
    4118             :     }
    4119             : 
    4120          49 :     return true;
    4121             : }
    4122             : 
    4123             : /************************************************************************/
    4124             : /*                        HasGpkgextRelationsTable()                    */
    4125             : /************************************************************************/
    4126             : 
    4127         997 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4128             : {
    4129        1994 :     const int nCount = SQLGetInteger(
    4130         997 :         hDB,
    4131             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4132             :         "AND type IN ('table', 'view')",
    4133             :         nullptr);
    4134         997 :     return nCount == 1;
    4135             : }
    4136             : 
    4137             : /************************************************************************/
    4138             : /*                    CreateRelationsTableIfNecessary()                 */
    4139             : /************************************************************************/
    4140             : 
    4141           7 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
    4142             : {
    4143           7 :     if (HasGpkgextRelationsTable())
    4144             :     {
    4145           5 :         return true;
    4146             :     }
    4147             : 
    4148           2 :     if (OGRERR_NONE !=
    4149           2 :         SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
    4150             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    4151             :                             "base_table_name TEXT NOT NULL,"
    4152             :                             "base_primary_column TEXT NOT NULL DEFAULT 'id',"
    4153             :                             "related_table_name TEXT NOT NULL,"
    4154             :                             "related_primary_column TEXT NOT NULL DEFAULT 'id',"
    4155             :                             "relation_name TEXT NOT NULL,"
    4156             :                             "mapping_table_name TEXT NOT NULL UNIQUE);"))
    4157             :     {
    4158           0 :         return false;
    4159             :     }
    4160             : 
    4161           2 :     return true;
    4162             : }
    4163             : 
    4164             : /************************************************************************/
    4165             : /*                        HasQGISLayerStyles()                          */
    4166             : /************************************************************************/
    4167             : 
    4168          11 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
    4169             : {
    4170             :     // QGIS layer_styles extension:
    4171             :     // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
    4172          11 :     bool bRet = false;
    4173             :     const int nCount =
    4174          11 :         SQLGetInteger(hDB,
    4175             :                       "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
    4176             :                       "AND type = 'table'",
    4177             :                       nullptr);
    4178          11 :     if (nCount == 1)
    4179             :     {
    4180           1 :         sqlite3_stmt *hSQLStmt = nullptr;
    4181           2 :         int rc = sqlite3_prepare_v2(
    4182           1 :             hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
    4183             :             &hSQLStmt, nullptr);
    4184           1 :         if (rc == SQLITE_OK)
    4185             :         {
    4186           1 :             bRet = true;
    4187           1 :             sqlite3_finalize(hSQLStmt);
    4188             :         }
    4189             :     }
    4190          11 :     return bRet;
    4191             : }
    4192             : 
    4193             : /************************************************************************/
    4194             : /*                            GetMetadata()                             */
    4195             : /************************************************************************/
    4196             : 
    4197        3090 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4198             : 
    4199             : {
    4200        3090 :     pszDomain = CheckMetadataDomain(pszDomain);
    4201        3090 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
    4202          50 :         return m_aosSubDatasets.List();
    4203             : 
    4204        3040 :     if (m_bHasReadMetadataFromStorage)
    4205        1342 :         return GDALPamDataset::GetMetadata(pszDomain);
    4206             : 
    4207        1698 :     m_bHasReadMetadataFromStorage = true;
    4208             : 
    4209        1698 :     TryLoadXML();
    4210             : 
    4211        1698 :     if (!HasMetadataTables())
    4212        1244 :         return GDALPamDataset::GetMetadata(pszDomain);
    4213             : 
    4214         454 :     char *pszSQL = nullptr;
    4215         454 :     if (!m_osRasterTable.empty())
    4216             :     {
    4217         160 :         pszSQL = sqlite3_mprintf(
    4218             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4219             :             "mdr.reference_scope FROM gpkg_metadata md "
    4220             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4221             :             "WHERE "
    4222             :             "(mdr.reference_scope = 'geopackage' OR "
    4223             :             "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
    4224             :             "lower('%q'))) ORDER BY md.id "
    4225             :             "LIMIT 1000",  // to avoid denial of service
    4226             :             m_osRasterTable.c_str());
    4227             :     }
    4228             :     else
    4229             :     {
    4230         294 :         pszSQL = sqlite3_mprintf(
    4231             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4232             :             "mdr.reference_scope FROM gpkg_metadata md "
    4233             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4234             :             "WHERE "
    4235             :             "mdr.reference_scope = 'geopackage' ORDER BY md.id "
    4236             :             "LIMIT 1000"  // to avoid denial of service
    4237             :         );
    4238             :     }
    4239             : 
    4240         908 :     auto oResult = SQLQuery(hDB, pszSQL);
    4241         454 :     sqlite3_free(pszSQL);
    4242         454 :     if (!oResult)
    4243             :     {
    4244           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4245             :     }
    4246             : 
    4247         454 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4248             : 
    4249             :     /* GDAL metadata */
    4250         635 :     for (int i = 0; i < oResult->RowCount(); i++)
    4251             :     {
    4252         181 :         const char *pszMetadata = oResult->GetValue(0, i);
    4253         181 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4254         181 :         const char *pszMimeType = oResult->GetValue(2, i);
    4255         181 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4256         181 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4257         181 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4258         165 :             EQUAL(pszMimeType, "text/xml"))
    4259             :         {
    4260         165 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4261         165 :             if (psXMLNode)
    4262             :             {
    4263         330 :                 GDALMultiDomainMetadata oLocalMDMD;
    4264         165 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4265         315 :                 if (!m_osRasterTable.empty() &&
    4266         150 :                     EQUAL(pszReferenceScope, "geopackage"))
    4267             :                 {
    4268           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4269             :                 }
    4270             :                 else
    4271             :                 {
    4272             :                     papszMetadata =
    4273         159 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4274         159 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4275         159 :                     CSLConstList papszIter = papszDomainList;
    4276         417 :                     while (papszIter && *papszIter)
    4277             :                     {
    4278         258 :                         if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
    4279             :                         {
    4280             :                             CSLConstList papszMD =
    4281         117 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4282             :                             const char *pszBAND_COUNT =
    4283         117 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4284         117 :                             if (pszBAND_COUNT)
    4285         115 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4286             : 
    4287             :                             const char *pszCOLOR_TABLE =
    4288         117 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4289         117 :                             if (pszCOLOR_TABLE)
    4290             :                             {
    4291             :                                 const CPLStringList aosTokens(
    4292             :                                     CSLTokenizeString2(pszCOLOR_TABLE, "{,",
    4293          26 :                                                        0));
    4294          13 :                                 if ((aosTokens.size() % 4) == 0)
    4295             :                                 {
    4296          13 :                                     const int nColors = aosTokens.size() / 4;
    4297             :                                     m_poCTFromMetadata =
    4298          13 :                                         std::make_unique<GDALColorTable>();
    4299        3341 :                                     for (int iColor = 0; iColor < nColors;
    4300             :                                          ++iColor)
    4301             :                                     {
    4302             :                                         GDALColorEntry sEntry;
    4303        3328 :                                         sEntry.c1 = static_cast<short>(
    4304        3328 :                                             atoi(aosTokens[4 * iColor + 0]));
    4305        3328 :                                         sEntry.c2 = static_cast<short>(
    4306        3328 :                                             atoi(aosTokens[4 * iColor + 1]));
    4307        3328 :                                         sEntry.c3 = static_cast<short>(
    4308        3328 :                                             atoi(aosTokens[4 * iColor + 2]));
    4309        3328 :                                         sEntry.c4 = static_cast<short>(
    4310        3328 :                                             atoi(aosTokens[4 * iColor + 3]));
    4311        3328 :                                         m_poCTFromMetadata->SetColorEntry(
    4312             :                                             iColor, &sEntry);
    4313             :                                     }
    4314             :                                 }
    4315             :                             }
    4316             : 
    4317             :                             const char *pszTILE_FORMAT =
    4318         117 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4319         117 :                             if (pszTILE_FORMAT)
    4320             :                             {
    4321           8 :                                 m_osTFFromMetadata = pszTILE_FORMAT;
    4322           8 :                                 oMDMD.SetMetadataItem("TILE_FORMAT",
    4323             :                                                       pszTILE_FORMAT,
    4324             :                                                       "IMAGE_STRUCTURE");
    4325             :                             }
    4326             : 
    4327             :                             const char *pszNodataValue =
    4328         117 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4329         117 :                             if (pszNodataValue)
    4330             :                             {
    4331           2 :                                 m_osNodataValueFromMetadata = pszNodataValue;
    4332             :                             }
    4333             :                         }
    4334             : 
    4335         141 :                         else if (!EQUAL(*papszIter, "") &&
    4336          12 :                                  !STARTS_WITH(*papszIter, "BAND_"))
    4337             :                         {
    4338          12 :                             oMDMD.SetMetadata(
    4339           6 :                                 oLocalMDMD.GetMetadata(*papszIter), *papszIter);
    4340             :                         }
    4341         258 :                         papszIter++;
    4342             :                     }
    4343             :                 }
    4344         165 :                 CPLDestroyXMLNode(psXMLNode);
    4345             :             }
    4346             :         }
    4347             :     }
    4348             : 
    4349         454 :     GDALPamDataset::SetMetadata(papszMetadata);
    4350         454 :     CSLDestroy(papszMetadata);
    4351         454 :     papszMetadata = nullptr;
    4352             : 
    4353             :     /* Add non-GDAL metadata now */
    4354         454 :     int nNonGDALMDILocal = 1;
    4355         454 :     int nNonGDALMDIGeopackage = 1;
    4356         635 :     for (int i = 0; i < oResult->RowCount(); i++)
    4357             :     {
    4358         181 :         const char *pszMetadata = oResult->GetValue(0, i);
    4359         181 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4360         181 :         const char *pszMimeType = oResult->GetValue(2, i);
    4361         181 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4362         181 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4363         181 :             pszMimeType == nullptr || pszReferenceScope == nullptr)
    4364             :         {
    4365             :             // should not happen as there are NOT NULL constraints
    4366             :             // But a database could lack such NOT NULL constraints or have
    4367             :             // large values that would cause a memory allocation failure.
    4368           0 :             continue;
    4369             :         }
    4370         181 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4371         181 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4372         165 :             EQUAL(pszMimeType, "text/xml"))
    4373         165 :             continue;
    4374             : 
    4375          16 :         if (!m_osRasterTable.empty() && bIsGPKGScope)
    4376             :         {
    4377           8 :             oMDMD.SetMetadataItem(
    4378             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
    4379             :                 pszMetadata, "GEOPACKAGE");
    4380           8 :             nNonGDALMDIGeopackage++;
    4381             :         }
    4382             :         /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
    4383             :         ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
    4384             :         {
    4385             :             char* apszMD[2];
    4386             :             apszMD[0] = (char*)pszMetadata;
    4387             :             apszMD[1] = NULL;
    4388             :             oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
    4389             :         }*/
    4390             :         else
    4391             :         {
    4392           8 :             oMDMD.SetMetadataItem(
    4393             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
    4394             :                 pszMetadata);
    4395           8 :             nNonGDALMDILocal++;
    4396             :         }
    4397             :     }
    4398             : 
    4399         454 :     return GDALPamDataset::GetMetadata(pszDomain);
    4400             : }
    4401             : 
    4402             : /************************************************************************/
    4403             : /*                            WriteMetadata()                           */
    4404             : /************************************************************************/
    4405             : 
    4406         622 : void GDALGeoPackageDataset::WriteMetadata(
    4407             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4408             :     const char *pszTableName)
    4409             : {
    4410         622 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4411         622 :     if (!HasMetadataTables())
    4412             :     {
    4413         459 :         if (bIsEmpty || !CreateMetadataTables())
    4414             :         {
    4415         211 :             CPLDestroyXMLNode(psXMLNode);
    4416         211 :             return;
    4417             :         }
    4418             :     }
    4419             : 
    4420         411 :     char *pszXML = nullptr;
    4421         411 :     if (!bIsEmpty)
    4422             :     {
    4423             :         CPLXMLNode *psMasterXMLNode =
    4424         281 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4425         281 :         psMasterXMLNode->psChild = psXMLNode;
    4426         281 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4427         281 :         CPLDestroyXMLNode(psMasterXMLNode);
    4428             :     }
    4429             :     // cppcheck-suppress uselessAssignmentPtrArg
    4430         411 :     psXMLNode = nullptr;
    4431             : 
    4432         411 :     char *pszSQL = nullptr;
    4433         411 :     if (pszTableName && pszTableName[0] != '\0')
    4434             :     {
    4435         289 :         pszSQL = sqlite3_mprintf(
    4436             :             "SELECT md.id FROM gpkg_metadata md "
    4437             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4438             :             "WHERE md.md_scope = 'dataset' AND "
    4439             :             "md.md_standard_uri='http://gdal.org' "
    4440             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
    4441             :             "lower(mdr.table_name) = lower('%q')",
    4442             :             pszTableName);
    4443             :     }
    4444             :     else
    4445             :     {
    4446         122 :         pszSQL = sqlite3_mprintf(
    4447             :             "SELECT md.id FROM gpkg_metadata md "
    4448             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4449             :             "WHERE md.md_scope = 'dataset' AND "
    4450             :             "md.md_standard_uri='http://gdal.org' "
    4451             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = "
    4452             :             "'geopackage'");
    4453             :     }
    4454             :     OGRErr err;
    4455         411 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4456         411 :     if (err != OGRERR_NONE)
    4457         386 :         mdId = -1;
    4458         411 :     sqlite3_free(pszSQL);
    4459             : 
    4460         411 :     if (bIsEmpty)
    4461             :     {
    4462         130 :         if (mdId >= 0)
    4463             :         {
    4464           6 :             SQLCommand(
    4465             :                 hDB,
    4466             :                 CPLSPrintf(
    4467             :                     "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
    4468             :                     mdId));
    4469           6 :             SQLCommand(
    4470             :                 hDB,
    4471             :                 CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
    4472             :         }
    4473             :     }
    4474             :     else
    4475             :     {
    4476         281 :         if (mdId >= 0)
    4477             :         {
    4478          19 :             pszSQL = sqlite3_mprintf(
    4479             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4480             :                 pszXML, mdId);
    4481             :         }
    4482             :         else
    4483             :         {
    4484             :             pszSQL =
    4485         262 :                 sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
    4486             :                                 "md_standard_uri, mime_type, metadata) VALUES "
    4487             :                                 "('dataset','http://gdal.org','text/xml','%q')",
    4488             :                                 pszXML);
    4489             :         }
    4490         281 :         SQLCommand(hDB, pszSQL);
    4491         281 :         sqlite3_free(pszSQL);
    4492             : 
    4493         281 :         CPLFree(pszXML);
    4494             : 
    4495         281 :         if (mdId < 0)
    4496             :         {
    4497         262 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4498         262 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4499             :             {
    4500         250 :                 pszSQL = sqlite3_mprintf(
    4501             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4502             :                     "table_name, timestamp, md_file_id) VALUES "
    4503             :                     "('table', '%q', %s, %d)",
    4504         500 :                     pszTableName, GetCurrentDateEscapedSQL().c_str(),
    4505             :                     static_cast<int>(nFID));
    4506             :             }
    4507             :             else
    4508             :             {
    4509          12 :                 pszSQL = sqlite3_mprintf(
    4510             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4511             :                     "timestamp, md_file_id) VALUES "
    4512             :                     "('geopackage', %s, %d)",
    4513          24 :                     GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
    4514             :             }
    4515             :         }
    4516             :         else
    4517             :         {
    4518          19 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4519             :                                      "timestamp = %s WHERE md_file_id = %d",
    4520          38 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4521             :         }
    4522         281 :         SQLCommand(hDB, pszSQL);
    4523         281 :         sqlite3_free(pszSQL);
    4524             :     }
    4525             : }
    4526             : 
    4527             : /************************************************************************/
    4528             : /*                        CreateMetadataTables()                        */
    4529             : /************************************************************************/
    4530             : 
    4531         260 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4532             : {
    4533             :     const bool bCreateTriggers =
    4534         260 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
    4535             : 
    4536             :     /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
    4537             :     CPLString osSQL = "CREATE TABLE gpkg_metadata ("
    4538             :                       "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
    4539             :                       "md_scope TEXT NOT NULL DEFAULT 'dataset',"
    4540             :                       "md_standard_uri TEXT NOT NULL,"
    4541             :                       "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
    4542             :                       "metadata TEXT NOT NULL DEFAULT ''"
    4543         520 :                       ")";
    4544             : 
    4545             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4546         260 :     const char *pszMetadataTriggers =
    4547             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
    4548             :         "BEFORE INSERT ON 'gpkg_metadata' "
    4549             :         "FOR EACH ROW BEGIN "
    4550             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
    4551             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4552             :         "collectionSession | series | dataset | featureType | feature | "
    4553             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4554             :         "taxonomy software | service | collectionHardware | "
    4555             :         "nonGeographicDataset | dimensionGroup') "
    4556             :         "WHERE NOT(NEW.md_scope IN "
    4557             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4558             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4559             :         "'catalogue','schema','taxonomy','software','service', "
    4560             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4561             :         "END; "
    4562             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
    4563             :         "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
    4564             :         "FOR EACH ROW BEGIN "
    4565             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
    4566             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4567             :         "collectionSession | series | dataset | featureType | feature | "
    4568             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4569             :         "taxonomy software | service | collectionHardware | "
    4570             :         "nonGeographicDataset | dimensionGroup') "
    4571             :         "WHERE NOT(NEW.md_scope IN "
    4572             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4573             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4574             :         "'catalogue','schema','taxonomy','software','service', "
    4575             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4576             :         "END";
    4577         260 :     if (bCreateTriggers)
    4578             :     {
    4579           0 :         osSQL += ";";
    4580           0 :         osSQL += pszMetadataTriggers;
    4581             :     }
    4582             : 
    4583             :     /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
    4584             :      * Table Definition SQL */
    4585             :     osSQL += ";"
    4586             :              "CREATE TABLE gpkg_metadata_reference ("
    4587             :              "reference_scope TEXT NOT NULL,"
    4588             :              "table_name TEXT,"
    4589             :              "column_name TEXT,"
    4590             :              "row_id_value INTEGER,"
    4591             :              "timestamp DATETIME NOT NULL DEFAULT "
    4592             :              "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    4593             :              "md_file_id INTEGER NOT NULL,"
    4594             :              "md_parent_id INTEGER,"
    4595             :              "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
    4596             :              "gpkg_metadata(id),"
    4597             :              "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
    4598             :              "gpkg_metadata(id)"
    4599         260 :              ")";
    4600             : 
    4601             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4602             :      * Definition SQL   */
    4603         260 :     const char *pszMetadataReferenceTriggers =
    4604             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
    4605             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4606             :         "FOR EACH ROW BEGIN "
    4607             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4608             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4609             :         "table\", \"column\", \"row\", \"row/col\"') "
    4610             :         "WHERE NOT NEW.reference_scope IN "
    4611             :         "('geopackage','table','column','row','row/col'); "
    4612             :         "END; "
    4613             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
    4614             :         "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
    4615             :         "FOR EACH ROW BEGIN "
    4616             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4617             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4618             :         "\"table\", \"column\", \"row\", \"row/col\"') "
    4619             :         "WHERE NOT NEW.reference_scope IN "
    4620             :         "('geopackage','table','column','row','row/col'); "
    4621             :         "END; "
    4622             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
    4623             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4624             :         "FOR EACH ROW BEGIN "
    4625             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4626             :         "violates constraint: column name must be NULL when reference_scope "
    4627             :         "is \"geopackage\", \"table\" or \"row\"') "
    4628             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4629             :         "AND NEW.column_name IS NOT NULL); "
    4630             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4631             :         "violates constraint: column name must be defined for the specified "
    4632             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4633             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4634             :         "AND NOT NEW.table_name IN ( "
    4635             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4636             :         "AND name = NEW.table_name "
    4637             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4638             :         "END; "
    4639             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
    4640             :         "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
    4641             :         "FOR EACH ROW BEGIN "
    4642             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4643             :         "violates constraint: column name must be NULL when reference_scope "
    4644             :         "is \"geopackage\", \"table\" or \"row\"') "
    4645             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4646             :         "AND NEW.column_name IS NOT NULL); "
    4647             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4648             :         "violates constraint: column name must be defined for the specified "
    4649             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4650             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4651             :         "AND NOT NEW.table_name IN ( "
    4652             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4653             :         "AND name = NEW.table_name "
    4654             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4655             :         "END; "
    4656             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
    4657             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4658             :         "FOR EACH ROW BEGIN "
    4659             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4660             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4661             :         "is \"geopackage\", \"table\" or \"column\"') "
    4662             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4663             :         "AND NEW.row_id_value IS NOT NULL; "
    4664             :         "END; "
    4665             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
    4666             :         "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
    4667             :         "FOR EACH ROW BEGIN "
    4668             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4669             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4670             :         "is \"geopackage\", \"table\" or \"column\"') "
    4671             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4672             :         "AND NEW.row_id_value IS NOT NULL; "
    4673             :         "END; "
    4674             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
    4675             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4676             :         "FOR EACH ROW BEGIN "
    4677             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4678             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4679             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4680             :         "WHERE NOT (NEW.timestamp GLOB "
    4681             :         "'[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-"
    4682             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4683             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4684             :         "END; "
    4685             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
    4686             :         "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
    4687             :         "FOR EACH ROW BEGIN "
    4688             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4689             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4690             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4691             :         "WHERE NOT (NEW.timestamp GLOB "
    4692             :         "'[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-"
    4693             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4694             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4695             :         "END";
    4696         260 :     if (bCreateTriggers)
    4697             :     {
    4698           0 :         osSQL += ";";
    4699           0 :         osSQL += pszMetadataReferenceTriggers;
    4700             :     }
    4701             : 
    4702         260 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4703           0 :         return false;
    4704             : 
    4705         260 :     osSQL += ";";
    4706             :     osSQL += "INSERT INTO gpkg_extensions "
    4707             :              "(table_name, column_name, extension_name, definition, scope) "
    4708             :              "VALUES "
    4709             :              "('gpkg_metadata', NULL, 'gpkg_metadata', "
    4710             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4711         260 :              "'read-write')";
    4712             : 
    4713         260 :     osSQL += ";";
    4714             :     osSQL += "INSERT INTO gpkg_extensions "
    4715             :              "(table_name, column_name, extension_name, definition, scope) "
    4716             :              "VALUES "
    4717             :              "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
    4718             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4719         260 :              "'read-write')";
    4720             : 
    4721         260 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4722         260 :     m_nHasMetadataTables = bOK;
    4723         260 :     return bOK;
    4724             : }
    4725             : 
    4726             : /************************************************************************/
    4727             : /*                            FlushMetadata()                           */
    4728             : /************************************************************************/
    4729             : 
    4730        7845 : void GDALGeoPackageDataset::FlushMetadata()
    4731             : {
    4732        7845 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4733         315 :         m_nCreateMetadataTables == FALSE)
    4734        7536 :         return;
    4735         309 :     m_bMetadataDirty = false;
    4736             : 
    4737         309 :     if (eAccess == GA_ReadOnly)
    4738             :     {
    4739           3 :         return;
    4740             :     }
    4741             : 
    4742         306 :     bool bCanWriteAreaOrPoint =
    4743         610 :         !m_bGridCellEncodingAsCO &&
    4744         304 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4745         306 :     if (!m_osRasterTable.empty())
    4746             :     {
    4747             :         const char *pszIdentifier =
    4748         124 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4749             :         const char *pszDescription =
    4750         124 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4751         152 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4752          28 :             pszIdentifier != m_osIdentifier)
    4753             :         {
    4754          14 :             m_osIdentifier = pszIdentifier;
    4755             :             char *pszSQL =
    4756          14 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4757             :                                 "WHERE lower(table_name) = lower('%q')",
    4758             :                                 pszIdentifier, m_osRasterTable.c_str());
    4759          14 :             SQLCommand(hDB, pszSQL);
    4760          14 :             sqlite3_free(pszSQL);
    4761             :         }
    4762         131 :         if (!m_bDescriptionAsCO && pszDescription != nullptr &&
    4763           7 :             pszDescription != m_osDescription)
    4764             :         {
    4765           7 :             m_osDescription = pszDescription;
    4766             :             char *pszSQL =
    4767           7 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4768             :                                 "WHERE lower(table_name) = lower('%q')",
    4769             :                                 pszDescription, m_osRasterTable.c_str());
    4770           7 :             SQLCommand(hDB, pszSQL);
    4771           7 :             sqlite3_free(pszSQL);
    4772             :         }
    4773         124 :         if (bCanWriteAreaOrPoint)
    4774             :         {
    4775             :             const char *pszAreaOrPoint =
    4776          28 :                 GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
    4777          28 :             if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
    4778             :             {
    4779          23 :                 bCanWriteAreaOrPoint = false;
    4780          23 :                 char *pszSQL = sqlite3_mprintf(
    4781             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4782             :                     "grid_cell_encoding = 'grid-value-is-area' WHERE "
    4783             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4784             :                     m_osRasterTable.c_str());
    4785          23 :                 SQLCommand(hDB, pszSQL);
    4786          23 :                 sqlite3_free(pszSQL);
    4787             :             }
    4788           5 :             else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
    4789             :             {
    4790           1 :                 bCanWriteAreaOrPoint = false;
    4791           1 :                 char *pszSQL = sqlite3_mprintf(
    4792             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4793             :                     "grid_cell_encoding = 'grid-value-is-center' WHERE "
    4794             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4795             :                     m_osRasterTable.c_str());
    4796           1 :                 SQLCommand(hDB, pszSQL);
    4797           1 :                 sqlite3_free(pszSQL);
    4798             :             }
    4799             :         }
    4800             :     }
    4801             : 
    4802         306 :     char **papszMDDup = nullptr;
    4803         491 :     for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
    4804         491 :          papszIter && *papszIter; ++papszIter)
    4805             :     {
    4806         185 :         if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4807          28 :             continue;
    4808         157 :         if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4809           8 :             continue;
    4810         149 :         if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
    4811          14 :             continue;
    4812         135 :         if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
    4813           4 :             continue;
    4814         131 :         if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
    4815          29 :             !bCanWriteAreaOrPoint &&
    4816          26 :             STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
    4817             :         {
    4818          26 :             continue;
    4819             :         }
    4820         105 :         papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4821             :     }
    4822             : 
    4823         306 :     CPLXMLNode *psXMLNode = nullptr;
    4824             :     {
    4825         306 :         GDALMultiDomainMetadata oLocalMDMD;
    4826         306 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4827         306 :         CSLConstList papszIter = papszDomainList;
    4828         306 :         oLocalMDMD.SetMetadata(papszMDDup);
    4829         589 :         while (papszIter && *papszIter)
    4830             :         {
    4831         283 :             if (!EQUAL(*papszIter, "") &&
    4832         139 :                 !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
    4833          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4834             :             {
    4835           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4836             :                                        *papszIter);
    4837             :             }
    4838         283 :             papszIter++;
    4839             :         }
    4840         306 :         if (m_nBandCountFromMetadata > 0)
    4841             :         {
    4842          59 :             oLocalMDMD.SetMetadataItem(
    4843             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4844             :                 "IMAGE_STRUCTURE");
    4845          59 :             if (nBands == 1)
    4846             :             {
    4847          37 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4848          37 :                 if (poCT)
    4849             :                 {
    4850          16 :                     std::string osVal("{");
    4851           8 :                     const int nColorCount = poCT->GetColorEntryCount();
    4852        2056 :                     for (int i = 0; i < nColorCount; ++i)
    4853             :                     {
    4854        2048 :                         if (i > 0)
    4855        2040 :                             osVal += ',';
    4856        2048 :                         const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
    4857             :                         osVal +=
    4858        2048 :                             CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
    4859        2048 :                                        psEntry->c2, psEntry->c3, psEntry->c4);
    4860             :                     }
    4861           8 :                     osVal += '}';
    4862           8 :                     oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
    4863             :                                                "IMAGE_STRUCTURE");
    4864             :                 }
    4865             :             }
    4866          59 :             if (nBands == 1)
    4867             :             {
    4868          37 :                 const char *pszTILE_FORMAT = nullptr;
    4869          37 :                 switch (m_eTF)
    4870             :                 {
    4871           0 :                     case GPKG_TF_PNG_JPEG:
    4872           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4873           0 :                         break;
    4874          31 :                     case GPKG_TF_PNG:
    4875          31 :                         break;
    4876           0 :                     case GPKG_TF_PNG8:
    4877           0 :                         pszTILE_FORMAT = "PNG8";
    4878           0 :                         break;
    4879           3 :                     case GPKG_TF_JPEG:
    4880           3 :                         pszTILE_FORMAT = "JPEG";
    4881           3 :                         break;
    4882           3 :                     case GPKG_TF_WEBP:
    4883           3 :                         pszTILE_FORMAT = "WEBP";
    4884           3 :                         break;
    4885           0 :                     case GPKG_TF_PNG_16BIT:
    4886           0 :                         break;
    4887           0 :                     case GPKG_TF_TIFF_32BIT_FLOAT:
    4888           0 :                         break;
    4889             :                 }
    4890          37 :                 if (pszTILE_FORMAT)
    4891           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4892             :                                                "IMAGE_STRUCTURE");
    4893             :             }
    4894             :         }
    4895         430 :         if (GetRasterCount() > 0 &&
    4896         124 :             GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
    4897             :         {
    4898          94 :             int bHasNoData = FALSE;
    4899             :             const double dfNoDataValue =
    4900          94 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4901          94 :             if (bHasNoData)
    4902             :             {
    4903           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4904             :                                            CPLSPrintf("%.17g", dfNoDataValue),
    4905             :                                            "IMAGE_STRUCTURE");
    4906             :             }
    4907             :         }
    4908         535 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4909             :         {
    4910             :             auto poBand =
    4911         229 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4912         229 :             poBand->AddImplicitStatistics(false);
    4913         229 :             char **papszMD = GetRasterBand(i)->GetMetadata();
    4914         229 :             poBand->AddImplicitStatistics(true);
    4915         229 :             if (papszMD)
    4916             :             {
    4917           8 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4918             :             }
    4919             :         }
    4920         306 :         psXMLNode = oLocalMDMD.Serialize();
    4921             :     }
    4922             : 
    4923         306 :     CSLDestroy(papszMDDup);
    4924         306 :     papszMDDup = nullptr;
    4925             : 
    4926         306 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4927             : 
    4928         306 :     if (!m_osRasterTable.empty())
    4929             :     {
    4930             :         char **papszGeopackageMD =
    4931         124 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4932             : 
    4933         124 :         papszMDDup = nullptr;
    4934         133 :         for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
    4935             :              ++papszIter)
    4936             :         {
    4937           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4938             :         }
    4939             : 
    4940         248 :         GDALMultiDomainMetadata oLocalMDMD;
    4941         124 :         oLocalMDMD.SetMetadata(papszMDDup);
    4942         124 :         CSLDestroy(papszMDDup);
    4943         124 :         papszMDDup = nullptr;
    4944         124 :         psXMLNode = oLocalMDMD.Serialize();
    4945             : 
    4946         124 :         WriteMetadata(psXMLNode, nullptr);
    4947             :     }
    4948             : 
    4949         498 :     for (int i = 0; i < m_nLayers; i++)
    4950             :     {
    4951             :         const char *pszIdentifier =
    4952         192 :             m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    4953             :         const char *pszDescription =
    4954         192 :             m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
    4955         192 :         if (pszIdentifier != nullptr)
    4956             :         {
    4957             :             char *pszSQL =
    4958           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4959             :                                 "WHERE lower(table_name) = lower('%q')",
    4960           3 :                                 pszIdentifier, m_papoLayers[i]->GetName());
    4961           3 :             SQLCommand(hDB, pszSQL);
    4962           3 :             sqlite3_free(pszSQL);
    4963             :         }
    4964         192 :         if (pszDescription != nullptr)
    4965             :         {
    4966             :             char *pszSQL =
    4967           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4968             :                                 "WHERE lower(table_name) = lower('%q')",
    4969           3 :                                 pszDescription, m_papoLayers[i]->GetName());
    4970           3 :             SQLCommand(hDB, pszSQL);
    4971           3 :             sqlite3_free(pszSQL);
    4972             :         }
    4973             : 
    4974         192 :         papszMDDup = nullptr;
    4975         548 :         for (char **papszIter = m_papoLayers[i]->GetMetadata();
    4976         548 :              papszIter && *papszIter; ++papszIter)
    4977             :         {
    4978         356 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4979           3 :                 continue;
    4980         353 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4981           3 :                 continue;
    4982         350 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    4983           0 :                 continue;
    4984         350 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4985             :         }
    4986             : 
    4987             :         {
    4988         192 :             GDALMultiDomainMetadata oLocalMDMD;
    4989         192 :             char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
    4990         192 :             char **papszIter = papszDomainList;
    4991         192 :             oLocalMDMD.SetMetadata(papszMDDup);
    4992         405 :             while (papszIter && *papszIter)
    4993             :             {
    4994         213 :                 if (!EQUAL(*papszIter, ""))
    4995          68 :                     oLocalMDMD.SetMetadata(
    4996          34 :                         m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
    4997         213 :                 papszIter++;
    4998             :             }
    4999         192 :             CSLDestroy(papszDomainList);
    5000         192 :             psXMLNode = oLocalMDMD.Serialize();
    5001             :         }
    5002             : 
    5003         192 :         CSLDestroy(papszMDDup);
    5004         192 :         papszMDDup = nullptr;
    5005             : 
    5006         192 :         WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
    5007             :     }
    5008             : }
    5009             : 
    5010             : /************************************************************************/
    5011             : /*                          GetMetadataItem()                           */
    5012             : /************************************************************************/
    5013             : 
    5014        1378 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    5015             :                                                    const char *pszDomain)
    5016             : {
    5017        1378 :     pszDomain = CheckMetadataDomain(pszDomain);
    5018        1378 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    5019             : }
    5020             : 
    5021             : /************************************************************************/
    5022             : /*                            SetMetadata()                             */
    5023             : /************************************************************************/
    5024             : 
    5025         114 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
    5026             :                                           const char *pszDomain)
    5027             : {
    5028         114 :     pszDomain = CheckMetadataDomain(pszDomain);
    5029         114 :     m_bMetadataDirty = true;
    5030         114 :     GetMetadata(); /* force loading from storage if needed */
    5031         114 :     return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    5032             : }
    5033             : 
    5034             : /************************************************************************/
    5035             : /*                          SetMetadataItem()                           */
    5036             : /************************************************************************/
    5037             : 
    5038          21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
    5039             :                                               const char *pszValue,
    5040             :                                               const char *pszDomain)
    5041             : {
    5042          21 :     pszDomain = CheckMetadataDomain(pszDomain);
    5043          21 :     m_bMetadataDirty = true;
    5044          21 :     GetMetadata(); /* force loading from storage if needed */
    5045          21 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    5046             : }
    5047             : 
    5048             : /************************************************************************/
    5049             : /*                                Create()                              */
    5050             : /************************************************************************/
    5051             : 
    5052         775 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    5053             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    5054             :                                   char **papszOptions)
    5055             : {
    5056        1550 :     CPLString osCommand;
    5057             : 
    5058             :     /* First, ensure there isn't any such file yet. */
    5059             :     VSIStatBufL sStatBuf;
    5060             : 
    5061         775 :     if (nBandsIn != 0)
    5062             :     {
    5063         200 :         if (eDT == GDT_Byte)
    5064             :         {
    5065         130 :             if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
    5066             :                 nBandsIn != 4)
    5067             :             {
    5068           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5069             :                          "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
    5070             :                          "3 (RGB) or 4 (RGBA) band dataset supported for "
    5071             :                          "Byte datatype");
    5072           1 :                 return FALSE;
    5073             :             }
    5074             :         }
    5075          70 :         else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
    5076             :         {
    5077          43 :             if (nBandsIn != 1)
    5078             :             {
    5079           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5080             :                          "Only single band dataset supported for non Byte "
    5081             :                          "datatype");
    5082           3 :                 return FALSE;
    5083             :             }
    5084             :         }
    5085             :         else
    5086             :         {
    5087          27 :             CPLError(CE_Failure, CPLE_NotSupported,
    5088             :                      "Only Byte, Int16, UInt16 or Float32 supported");
    5089          27 :             return FALSE;
    5090             :         }
    5091             :     }
    5092             : 
    5093         744 :     const size_t nFilenameLen = strlen(pszFilename);
    5094         744 :     const bool bGpkgZip =
    5095         739 :         (nFilenameLen > strlen(".gpkg.zip") &&
    5096        1483 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    5097         739 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    5098             : 
    5099             :     const bool bUseTempFile =
    5100         745 :         bGpkgZip || (CPLTestBool(CPLGetConfigOption(
    5101           1 :                          "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
    5102           1 :                      (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
    5103           1 :                       EQUAL(CPLGetConfigOption(
    5104             :                                 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
    5105         744 :                             "FORCED")));
    5106             : 
    5107         744 :     bool bFileExists = false;
    5108         744 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
    5109             :     {
    5110           7 :         bFileExists = true;
    5111          14 :         if (nBandsIn == 0 || bUseTempFile ||
    5112           7 :             !CPLTestBool(
    5113             :                 CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
    5114             :         {
    5115           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5116             :                      "A file system object called '%s' already exists.",
    5117             :                      pszFilename);
    5118             : 
    5119           0 :             return FALSE;
    5120             :         }
    5121             :     }
    5122             : 
    5123         744 :     if (bUseTempFile)
    5124             :     {
    5125           3 :         if (bGpkgZip)
    5126             :         {
    5127           2 :             std::string osFilenameInZip(CPLGetFilename(pszFilename));
    5128           2 :             osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
    5129             :             m_osFinalFilename =
    5130           2 :                 std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
    5131             :         }
    5132             :         else
    5133             :         {
    5134           1 :             m_osFinalFilename = pszFilename;
    5135             :         }
    5136           3 :         m_pszFilename =
    5137           3 :             CPLStrdup(CPLGenerateTempFilename(CPLGetFilename(pszFilename)));
    5138           3 :         CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
    5139             :     }
    5140             :     else
    5141             :     {
    5142         741 :         m_pszFilename = CPLStrdup(pszFilename);
    5143             :     }
    5144         744 :     m_bNew = true;
    5145         744 :     eAccess = GA_Update;
    5146         744 :     m_bDateTimeWithTZ =
    5147         744 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5148             :               "WITH_TZ");
    5149             : 
    5150             :     // for test/debug purposes only. true is the nominal value
    5151         744 :     m_bPNGSupports2Bands =
    5152         744 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5153         744 :     m_bPNGSupportsCT =
    5154         744 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5155             : 
    5156         744 :     if (!OpenOrCreateDB(bFileExists
    5157             :                             ? SQLITE_OPEN_READWRITE
    5158             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5159           4 :         return FALSE;
    5160             : 
    5161             :     /* Default to synchronous=off for performance for new file */
    5162        1473 :     if (!bFileExists &&
    5163         733 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5164             :     {
    5165         289 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5166             :     }
    5167             : 
    5168             :     /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
    5169             :     /* will be written into the main file and supported henceforth */
    5170         740 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5171             : 
    5172         740 :     if (bFileExists)
    5173             :     {
    5174           7 :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    5175           7 :         if (fp)
    5176             :         {
    5177             :             GByte abyHeader[100];
    5178           7 :             VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
    5179           7 :             VSIFCloseL(fp);
    5180             : 
    5181           7 :             memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
    5182           7 :             m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    5183           7 :             memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
    5184           7 :             m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    5185             : 
    5186           7 :             if (m_nApplicationId == GP10_APPLICATION_ID)
    5187             :             {
    5188           0 :                 CPLDebug("GPKG", "GeoPackage v1.0");
    5189             :             }
    5190           7 :             else if (m_nApplicationId == GP11_APPLICATION_ID)
    5191             :             {
    5192           0 :                 CPLDebug("GPKG", "GeoPackage v1.1");
    5193             :             }
    5194           7 :             else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    5195           7 :                      m_nUserVersion >= GPKG_1_2_VERSION)
    5196             :             {
    5197           7 :                 CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    5198           7 :                          (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    5199             :             }
    5200             :         }
    5201             : 
    5202           7 :         DetectSpatialRefSysColumns();
    5203             :     }
    5204             : 
    5205         740 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5206         740 :     if (pszVersion && !EQUAL(pszVersion, "AUTO"))
    5207             :     {
    5208          33 :         if (EQUAL(pszVersion, "1.0"))
    5209             :         {
    5210           2 :             m_nApplicationId = GP10_APPLICATION_ID;
    5211           2 :             m_nUserVersion = 0;
    5212             :         }
    5213          31 :         else if (EQUAL(pszVersion, "1.1"))
    5214             :         {
    5215           1 :             m_nApplicationId = GP11_APPLICATION_ID;
    5216           1 :             m_nUserVersion = 0;
    5217             :         }
    5218          30 :         else if (EQUAL(pszVersion, "1.2"))
    5219             :         {
    5220          12 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5221          12 :             m_nUserVersion = GPKG_1_2_VERSION;
    5222             :         }
    5223          18 :         else if (EQUAL(pszVersion, "1.3"))
    5224             :         {
    5225           3 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5226           3 :             m_nUserVersion = GPKG_1_3_VERSION;
    5227             :         }
    5228          15 :         else if (EQUAL(pszVersion, "1.4"))
    5229             :         {
    5230          15 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5231          15 :             m_nUserVersion = GPKG_1_4_VERSION;
    5232             :         }
    5233             :     }
    5234             : 
    5235         740 :     SoftStartTransaction();
    5236             : 
    5237        1480 :     CPLString osSQL;
    5238         740 :     if (!bFileExists)
    5239             :     {
    5240             :         /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
    5241             :          * table */
    5242             :         /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5243             :         osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
    5244             :                 "srs_name TEXT NOT NULL,"
    5245             :                 "srs_id INTEGER NOT NULL PRIMARY KEY,"
    5246             :                 "organization TEXT NOT NULL,"
    5247             :                 "organization_coordsys_id INTEGER NOT NULL,"
    5248             :                 "definition  TEXT NOT NULL,"
    5249         733 :                 "description TEXT";
    5250         733 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5251         892 :                                              "NO")) ||
    5252         159 :             (nBandsIn != 0 && eDT != GDT_Byte))
    5253             :         {
    5254          42 :             m_bHasDefinition12_063 = true;
    5255          42 :             osSQL += ", definition_12_063 TEXT NOT NULL";
    5256          42 :             if (m_nUserVersion >= GPKG_1_4_VERSION)
    5257             :             {
    5258           1 :                 osSQL += ", epoch DOUBLE";
    5259           1 :                 m_bHasEpochColumn = true;
    5260             :             }
    5261             :         }
    5262             :         osSQL += ")"
    5263             :                  ";"
    5264             :                  /* Requirement 11: The gpkg_spatial_ref_sys table in a
    5265             :                     GeoPackage SHALL */
    5266             :                  /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
    5267             :                  /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5268             : 
    5269             :                  "INSERT INTO gpkg_spatial_ref_sys ("
    5270             :                  "srs_name, srs_id, organization, organization_coordsys_id, "
    5271         733 :                  "definition, description";
    5272         733 :         if (m_bHasDefinition12_063)
    5273          42 :             osSQL += ", definition_12_063";
    5274             :         osSQL +=
    5275             :             ") VALUES ("
    5276             :             "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
    5277             :             "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
    5278             :             "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
    5279             :             "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
    5280             :             "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
    5281             :             "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
    5282             :             "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
    5283             :             "', 'longitude/latitude coordinates in decimal degrees on the WGS "
    5284         733 :             "84 spheroid'";
    5285         733 :         if (m_bHasDefinition12_063)
    5286             :             osSQL +=
    5287             :                 ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
    5288             :                 "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
    5289             :                 "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
    5290             :                 "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
    5291             :                 "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
    5292             :                 "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
    5293          42 :                 "ID[\"EPSG\", 4326]]'";
    5294             :         osSQL +=
    5295             :             ")"
    5296             :             ";"
    5297             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5298             :                SHALL */
    5299             :             /* contain a record with an srs_id of -1, an organization of “NONE”,
    5300             :              */
    5301             :             /* an organization_coordsys_id of -1, and definition “undefined” */
    5302             :             /* for undefined Cartesian coordinate reference systems */
    5303             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5304             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5305             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5306         733 :             "definition, description";
    5307         733 :         if (m_bHasDefinition12_063)
    5308          42 :             osSQL += ", definition_12_063";
    5309             :         osSQL += ") VALUES ("
    5310             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5311         733 :                  "'undefined Cartesian coordinate reference system'";
    5312         733 :         if (m_bHasDefinition12_063)
    5313          42 :             osSQL += ", 'undefined'";
    5314             :         osSQL +=
    5315             :             ")"
    5316             :             ";"
    5317             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5318             :                SHALL */
    5319             :             /* contain a record with an srs_id of 0, an organization of “NONE”,
    5320             :              */
    5321             :             /* an organization_coordsys_id of 0, and definition “undefined” */
    5322             :             /* for undefined geographic coordinate reference systems */
    5323             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5324             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5325             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5326         733 :             "definition, description";
    5327         733 :         if (m_bHasDefinition12_063)
    5328          42 :             osSQL += ", definition_12_063";
    5329             :         osSQL += ") VALUES ("
    5330             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5331         733 :                  "'undefined geographic coordinate reference system'";
    5332         733 :         if (m_bHasDefinition12_063)
    5333          42 :             osSQL += ", 'undefined'";
    5334             :         osSQL += ")"
    5335             :                  ";"
    5336             :                  /* Requirement 13: A GeoPackage file SHALL include a
    5337             :                     gpkg_contents table */
    5338             :                  /* http://opengis.github.io/geopackage/#_contents */
    5339             :                  "CREATE TABLE gpkg_contents ("
    5340             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5341             :                  "data_type TEXT NOT NULL,"
    5342             :                  "identifier TEXT UNIQUE,"
    5343             :                  "description TEXT DEFAULT '',"
    5344             :                  "last_change DATETIME NOT NULL DEFAULT "
    5345             :                  "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    5346             :                  "min_x DOUBLE, min_y DOUBLE,"
    5347             :                  "max_x DOUBLE, max_y DOUBLE,"
    5348             :                  "srs_id INTEGER,"
    5349             :                  "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
    5350             :                  "gpkg_spatial_ref_sys(srs_id)"
    5351         733 :                  ")";
    5352             : 
    5353             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5354         733 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5355             :         {
    5356         731 :             m_bHasGPKGOGRContents = true;
    5357             :             osSQL += ";"
    5358             :                      "CREATE TABLE gpkg_ogr_contents("
    5359             :                      "table_name TEXT NOT NULL PRIMARY KEY,"
    5360             :                      "feature_count INTEGER DEFAULT NULL"
    5361         731 :                      ")";
    5362             :         }
    5363             : #endif
    5364             : 
    5365             :         /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
    5366             :          * “features” */
    5367             :         /* data_type SHALL contain a gpkg_geometry_columns table or updateable
    5368             :          * view */
    5369             :         /* http://opengis.github.io/geopackage/#_geometry_columns */
    5370             :         const bool bCreateGeometryColumns =
    5371         733 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5372         733 :         if (bCreateGeometryColumns)
    5373             :         {
    5374         732 :             m_bHasGPKGGeometryColumns = true;
    5375         732 :             osSQL += ";";
    5376         732 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5377             :         }
    5378             :     }
    5379             : 
    5380             :     const bool bCreateTriggers =
    5381         740 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
    5382           7 :     if ((bFileExists && nBandsIn != 0 &&
    5383           7 :          SQLGetInteger(
    5384             :              hDB,
    5385             :              "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
    5386             :              "AND type in ('table', 'view')",
    5387        1480 :              nullptr) == 0) ||
    5388         739 :         (!bFileExists &&
    5389         733 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5390             :     {
    5391         733 :         if (!osSQL.empty())
    5392         732 :             osSQL += ";";
    5393             : 
    5394             :         /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
    5395             :          * Creation SQL  */
    5396             :         osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
    5397             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5398             :                  "srs_id INTEGER NOT NULL,"
    5399             :                  "min_x DOUBLE NOT NULL,"
    5400             :                  "min_y DOUBLE NOT NULL,"
    5401             :                  "max_x DOUBLE NOT NULL,"
    5402             :                  "max_y DOUBLE NOT NULL,"
    5403             :                  "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
    5404             :                  "REFERENCES gpkg_contents(table_name),"
    5405             :                  "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
    5406             :                  "gpkg_spatial_ref_sys (srs_id)"
    5407             :                  ")"
    5408             :                  ";"
    5409             : 
    5410             :                  /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
    5411             :                     Creation SQL */
    5412             :                  "CREATE TABLE gpkg_tile_matrix ("
    5413             :                  "table_name TEXT NOT NULL,"
    5414             :                  "zoom_level INTEGER NOT NULL,"
    5415             :                  "matrix_width INTEGER NOT NULL,"
    5416             :                  "matrix_height INTEGER NOT NULL,"
    5417             :                  "tile_width INTEGER NOT NULL,"
    5418             :                  "tile_height INTEGER NOT NULL,"
    5419             :                  "pixel_x_size DOUBLE NOT NULL,"
    5420             :                  "pixel_y_size DOUBLE NOT NULL,"
    5421             :                  "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
    5422             :                  "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
    5423             :                  "REFERENCES gpkg_contents(table_name)"
    5424         733 :                  ")";
    5425             : 
    5426         733 :         if (bCreateTriggers)
    5427             :         {
    5428             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5429             :              * Definition SQL */
    5430         733 :             const char *pszTileMatrixTrigger =
    5431             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
    5432             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5433             :                 "FOR EACH ROW BEGIN "
    5434             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5435             :                 "violates constraint: zoom_level cannot be less than 0') "
    5436             :                 "WHERE (NEW.zoom_level < 0); "
    5437             :                 "END; "
    5438             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
    5439             :                 "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
    5440             :                 "FOR EACH ROW BEGIN "
    5441             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5442             :                 "violates constraint: zoom_level cannot be less than 0') "
    5443             :                 "WHERE (NEW.zoom_level < 0); "
    5444             :                 "END; "
    5445             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
    5446             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5447             :                 "FOR EACH ROW BEGIN "
    5448             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5449             :                 "violates constraint: matrix_width cannot be less than 1') "
    5450             :                 "WHERE (NEW.matrix_width < 1); "
    5451             :                 "END; "
    5452             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
    5453             :                 "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
    5454             :                 "FOR EACH ROW BEGIN "
    5455             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5456             :                 "violates constraint: matrix_width cannot be less than 1') "
    5457             :                 "WHERE (NEW.matrix_width < 1); "
    5458             :                 "END; "
    5459             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
    5460             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5461             :                 "FOR EACH ROW BEGIN "
    5462             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5463             :                 "violates constraint: matrix_height cannot be less than 1') "
    5464             :                 "WHERE (NEW.matrix_height < 1); "
    5465             :                 "END; "
    5466             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
    5467             :                 "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
    5468             :                 "FOR EACH ROW BEGIN "
    5469             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5470             :                 "violates constraint: matrix_height cannot be less than 1') "
    5471             :                 "WHERE (NEW.matrix_height < 1); "
    5472             :                 "END; "
    5473             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
    5474             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5475             :                 "FOR EACH ROW BEGIN "
    5476             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5477             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5478             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5479             :                 "END; "
    5480             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
    5481             :                 "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
    5482             :                 "FOR EACH ROW BEGIN "
    5483             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5484             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5485             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5486             :                 "END; "
    5487             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
    5488             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5489             :                 "FOR EACH ROW BEGIN "
    5490             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5491             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5492             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5493             :                 "END; "
    5494             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
    5495             :                 "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
    5496             :                 "FOR EACH ROW BEGIN "
    5497             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5498             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5499             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5500             :                 "END;";
    5501         733 :             osSQL += ";";
    5502         733 :             osSQL += pszTileMatrixTrigger;
    5503             :         }
    5504             :     }
    5505             : 
    5506         740 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5507           0 :         return FALSE;
    5508             : 
    5509         740 :     if (!bFileExists)
    5510             :     {
    5511             :         const char *pszMetadataTables =
    5512         733 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5513         733 :         if (pszMetadataTables)
    5514           6 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5515             : 
    5516         733 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5517           0 :             return FALSE;
    5518             : 
    5519         733 :         if (m_bHasDefinition12_063)
    5520             :         {
    5521          84 :             if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
    5522             :                 OGRERR_NONE !=
    5523          42 :                     SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5524             :                                     "(table_name, column_name, extension_name, "
    5525             :                                     "definition, scope) "
    5526             :                                     "VALUES "
    5527             :                                     "('gpkg_spatial_ref_sys', "
    5528             :                                     "'definition_12_063', 'gpkg_crs_wkt', "
    5529             :                                     "'http://www.geopackage.org/spec120/"
    5530             :                                     "#extension_crs_wkt', 'read-write')"))
    5531             :             {
    5532           0 :                 return FALSE;
    5533             :             }
    5534          42 :             if (m_bHasEpochColumn)
    5535             :             {
    5536           1 :                 if (OGRERR_NONE !=
    5537           1 :                         SQLCommand(
    5538             :                             hDB, "UPDATE gpkg_extensions SET extension_name = "
    5539             :                                  "'gpkg_crs_wkt_1_1' "
    5540           2 :                                  "WHERE extension_name = 'gpkg_crs_wkt'") ||
    5541             :                     OGRERR_NONE !=
    5542           1 :                         SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5543             :                                         "(table_name, column_name, "
    5544             :                                         "extension_name, definition, scope) "
    5545             :                                         "VALUES "
    5546             :                                         "('gpkg_spatial_ref_sys', 'epoch', "
    5547             :                                         "'gpkg_crs_wkt_1_1', "
    5548             :                                         "'http://www.geopackage.org/spec/"
    5549             :                                         "#extension_crs_wkt', "
    5550             :                                         "'read-write')"))
    5551             :                 {
    5552           0 :                     return FALSE;
    5553             :                 }
    5554             :             }
    5555             :         }
    5556             :     }
    5557             : 
    5558         740 :     if (nBandsIn != 0)
    5559             :     {
    5560         166 :         const char *pszTableName = CPLGetBasename(m_pszFilename);
    5561             :         m_osRasterTable =
    5562         166 :             CSLFetchNameValueDef(papszOptions, "RASTER_TABLE", pszTableName);
    5563         166 :         if (m_osRasterTable.empty())
    5564             :         {
    5565           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5566             :                      "RASTER_TABLE must be set to a non empty value");
    5567           0 :             return FALSE;
    5568             :         }
    5569         166 :         m_bIdentifierAsCO =
    5570         166 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5571             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5572         166 :                                               m_osRasterTable);
    5573         166 :         m_bDescriptionAsCO =
    5574         166 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5575             :         m_osDescription =
    5576         166 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5577         166 :         SetDataType(eDT);
    5578         166 :         if (eDT == GDT_Int16)
    5579          16 :             SetGlobalOffsetScale(-32768.0, 1.0);
    5580             : 
    5581             :         /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
    5582             :          * table Create Table SQL (Informative) */
    5583             :         char *pszSQL =
    5584         166 :             sqlite3_mprintf("CREATE TABLE \"%w\" ("
    5585             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    5586             :                             "zoom_level INTEGER NOT NULL,"
    5587             :                             "tile_column INTEGER NOT NULL,"
    5588             :                             "tile_row INTEGER NOT NULL,"
    5589             :                             "tile_data BLOB NOT NULL,"
    5590             :                             "UNIQUE (zoom_level, tile_column, tile_row)"
    5591             :                             ")",
    5592             :                             m_osRasterTable.c_str());
    5593         166 :         osSQL = pszSQL;
    5594         166 :         sqlite3_free(pszSQL);
    5595             : 
    5596         166 :         if (bCreateTriggers)
    5597             :         {
    5598         166 :             osSQL += ";";
    5599         166 :             osSQL += CreateRasterTriggersSQL(m_osRasterTable);
    5600             :         }
    5601             : 
    5602         166 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5603         166 :         if (OGRERR_NONE != eErr)
    5604           0 :             return FALSE;
    5605             : 
    5606         166 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5607         166 :         if (eDT == GDT_Int16 || eDT == GDT_UInt16)
    5608             :         {
    5609          27 :             m_eTF = GPKG_TF_PNG_16BIT;
    5610          27 :             if (pszTF)
    5611             :             {
    5612           1 :                 if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
    5613             :                 {
    5614           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5615             :                              "Only AUTO or PNG supported "
    5616             :                              "as tile format for Int16 / UInt16");
    5617             :                 }
    5618             :             }
    5619             :         }
    5620         139 :         else if (eDT == GDT_Float32)
    5621             :         {
    5622          13 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    5623          13 :             if (pszTF)
    5624             :             {
    5625           5 :                 if (EQUAL(pszTF, "PNG"))
    5626           5 :                     m_eTF = GPKG_TF_PNG_16BIT;
    5627           0 :                 else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
    5628             :                 {
    5629           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5630             :                              "Only AUTO, PNG or TIFF supported "
    5631             :                              "as tile format for Float32");
    5632             :                 }
    5633             :             }
    5634             :         }
    5635             :         else
    5636             :         {
    5637         126 :             if (pszTF)
    5638             :             {
    5639          71 :                 m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    5640          71 :                 if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
    5641           7 :                     m_bMetadataDirty = true;
    5642             :             }
    5643          55 :             else if (nBandsIn == 1)
    5644          44 :                 m_eTF = GPKG_TF_PNG;
    5645             :         }
    5646             : 
    5647         166 :         if (eDT != GDT_Byte)
    5648             :         {
    5649          40 :             if (!CreateTileGriddedTable(papszOptions))
    5650           0 :                 return FALSE;
    5651             :         }
    5652             : 
    5653         166 :         nRasterXSize = nXSize;
    5654         166 :         nRasterYSize = nYSize;
    5655             : 
    5656             :         const char *pszTileSize =
    5657         166 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5658             :         const char *pszTileWidth =
    5659         166 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5660             :         const char *pszTileHeight =
    5661         166 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5662         166 :         int nTileWidth = atoi(pszTileWidth);
    5663         166 :         int nTileHeight = atoi(pszTileHeight);
    5664         166 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5665         332 :              nTileHeight > 4096) &&
    5666           1 :             !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
    5667             :         {
    5668           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5669             :                      "Invalid block dimensions: %dx%d", nTileWidth,
    5670             :                      nTileHeight);
    5671           0 :             return FALSE;
    5672             :         }
    5673             : 
    5674         465 :         for (int i = 1; i <= nBandsIn; i++)
    5675         299 :             SetBand(
    5676         299 :                 i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
    5677             : 
    5678         166 :         GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    5679             :                                         "IMAGE_STRUCTURE");
    5680         166 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5681         166 :         if (!m_osDescription.empty())
    5682           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5683             : 
    5684         166 :         ParseCompressionOptions(papszOptions);
    5685             : 
    5686         166 :         if (m_eTF == GPKG_TF_WEBP)
    5687             :         {
    5688          10 :             if (!RegisterWebPExtension())
    5689           0 :                 return FALSE;
    5690             :         }
    5691             : 
    5692             :         m_osTilingScheme =
    5693         166 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5694         166 :         if (!EQUAL(m_osTilingScheme, "CUSTOM"))
    5695             :         {
    5696          22 :             const auto poTS = GetTilingScheme(m_osTilingScheme);
    5697          22 :             if (!poTS)
    5698           0 :                 return FALSE;
    5699             : 
    5700          43 :             if (nTileWidth != poTS->nTileWidth ||
    5701          21 :                 nTileHeight != poTS->nTileHeight)
    5702             :             {
    5703           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5704             :                          "Tile dimension should be %dx%d for %s tiling scheme",
    5705           1 :                          poTS->nTileWidth, poTS->nTileHeight,
    5706             :                          m_osTilingScheme.c_str());
    5707           1 :                 return FALSE;
    5708             :             }
    5709             : 
    5710             :             const char *pszZoomLevel =
    5711          21 :                 CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    5712          21 :             if (pszZoomLevel)
    5713             :             {
    5714           1 :                 m_nZoomLevel = atoi(pszZoomLevel);
    5715           1 :                 int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    5716           1 :                 while ((1 << nMaxZoomLevelForThisTM) >
    5717           2 :                            INT_MAX / poTS->nTileXCountZoomLevel0 ||
    5718           1 :                        (1 << nMaxZoomLevelForThisTM) >
    5719           1 :                            INT_MAX / poTS->nTileYCountZoomLevel0)
    5720             :                 {
    5721           0 :                     --nMaxZoomLevelForThisTM;
    5722             :                 }
    5723             : 
    5724           1 :                 if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
    5725             :                 {
    5726           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5727             :                              "ZOOM_LEVEL = %s is invalid. It should be in "
    5728             :                              "[0,%d] range",
    5729             :                              pszZoomLevel, nMaxZoomLevelForThisTM);
    5730           0 :                     return FALSE;
    5731             :                 }
    5732             :             }
    5733             : 
    5734             :             // Implicitly sets SRS.
    5735          21 :             OGRSpatialReference oSRS;
    5736          21 :             if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
    5737           0 :                 return FALSE;
    5738          21 :             char *pszWKT = nullptr;
    5739          21 :             oSRS.exportToWkt(&pszWKT);
    5740          21 :             SetProjection(pszWKT);
    5741          21 :             CPLFree(pszWKT);
    5742             :         }
    5743             :         else
    5744             :         {
    5745         144 :             if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5746             :             {
    5747           0 :                 CPLError(
    5748             :                     CE_Failure, CPLE_NotSupported,
    5749             :                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5750           0 :                 return false;
    5751             :             }
    5752             :         }
    5753             :     }
    5754             : 
    5755         739 :     if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
    5756             :     {
    5757             :         // If there was an ogr_empty_table table, we can remove it
    5758           6 :         RemoveOGREmptyTable();
    5759             :     }
    5760             : 
    5761         739 :     SoftCommitTransaction();
    5762             : 
    5763             :     /* Requirement 2 */
    5764             :     /* We have to do this after there's some content so the database file */
    5765             :     /* is not zero length */
    5766         739 :     SetApplicationAndUserVersionId();
    5767             : 
    5768             :     /* Default to synchronous=off for performance for new file */
    5769        1471 :     if (!bFileExists &&
    5770         732 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5771             :     {
    5772         289 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5773             :     }
    5774             : 
    5775         739 :     return TRUE;
    5776             : }
    5777             : 
    5778             : /************************************************************************/
    5779             : /*                        RemoveOGREmptyTable()                         */
    5780             : /************************************************************************/
    5781             : 
    5782         562 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5783             : {
    5784             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5785         562 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5786             :                  nullptr);
    5787         562 :     sqlite3_exec(
    5788             :         hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
    5789             :         nullptr, nullptr, nullptr);
    5790             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5791         562 :     if (m_bHasGPKGOGRContents)
    5792             :     {
    5793         553 :         sqlite3_exec(hDB,
    5794             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5795             :                      "table_name = 'ogr_empty_table'",
    5796             :                      nullptr, nullptr, nullptr);
    5797             :     }
    5798             : #endif
    5799         562 :     sqlite3_exec(hDB,
    5800             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5801             :                  "table_name = 'ogr_empty_table'",
    5802             :                  nullptr, nullptr, nullptr);
    5803         562 : }
    5804             : 
    5805             : /************************************************************************/
    5806             : /*                        CreateTileGriddedTable()                      */
    5807             : /************************************************************************/
    5808             : 
    5809          40 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
    5810             : {
    5811          80 :     CPLString osSQL;
    5812          40 :     if (!HasGriddedCoverageAncillaryTable())
    5813             :     {
    5814             :         // It doesn't exist. So create gpkg_extensions table if necessary, and
    5815             :         // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
    5816             :         // and register them as extensions.
    5817          40 :         if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    5818           0 :             return false;
    5819             : 
    5820             :         // Req 1 /table-defs/coverage-ancillary
    5821             :         osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
    5822             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5823             :                 "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
    5824             :                 "datatype TEXT NOT NULL DEFAULT 'integer',"
    5825             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5826             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5827             :                 "precision REAL DEFAULT 1.0,"
    5828             :                 "data_null REAL,"
    5829             :                 "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
    5830             :                 "uom TEXT,"
    5831             :                 "field_name TEXT DEFAULT 'Height',"
    5832             :                 "quantity_definition TEXT DEFAULT 'Height',"
    5833             :                 "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
    5834             :                 "REFERENCES gpkg_tile_matrix_set ( table_name ) "
    5835             :                 "CHECK (datatype in ('integer','float')))"
    5836             :                 ";"
    5837             :                 // Requirement 2 /table-defs/tile-ancillary
    5838             :                 "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
    5839             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5840             :                 "tpudt_name TEXT NOT NULL,"
    5841             :                 "tpudt_id INTEGER NOT NULL,"
    5842             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5843             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5844             :                 "min REAL DEFAULT NULL,"
    5845             :                 "max REAL DEFAULT NULL,"
    5846             :                 "mean REAL DEFAULT NULL,"
    5847             :                 "std_dev REAL DEFAULT NULL,"
    5848             :                 "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
    5849             :                 "REFERENCES gpkg_contents(table_name),"
    5850             :                 "UNIQUE (tpudt_name, tpudt_id))"
    5851             :                 ";"
    5852             :                 // Requirement 6 /gpkg-extensions
    5853             :                 "INSERT INTO gpkg_extensions "
    5854             :                 "(table_name, column_name, extension_name, definition, scope) "
    5855             :                 "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
    5856             :                 "'gpkg_2d_gridded_coverage', "
    5857             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5858             :                 "'read-write')"
    5859             :                 ";"
    5860             :                 // Requirement 6 /gpkg-extensions
    5861             :                 "INSERT INTO gpkg_extensions "
    5862             :                 "(table_name, column_name, extension_name, definition, scope) "
    5863             :                 "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
    5864             :                 "'gpkg_2d_gridded_coverage', "
    5865             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5866             :                 "'read-write')"
    5867          40 :                 ";";
    5868             :     }
    5869             : 
    5870             :     // Requirement 6 /gpkg-extensions
    5871          40 :     char *pszSQL = sqlite3_mprintf(
    5872             :         "INSERT INTO gpkg_extensions "
    5873             :         "(table_name, column_name, extension_name, definition, scope) "
    5874             :         "VALUES ('%q', 'tile_data', "
    5875             :         "'gpkg_2d_gridded_coverage', "
    5876             :         "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5877             :         "'read-write')",
    5878             :         m_osRasterTable.c_str());
    5879          40 :     osSQL += pszSQL;
    5880          40 :     osSQL += ";";
    5881          40 :     sqlite3_free(pszSQL);
    5882             : 
    5883             :     // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
    5884             :     // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
    5885             :     // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
    5886          40 :     m_dfPrecision =
    5887          40 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
    5888             :     CPLString osGridCellEncoding(CSLFetchNameValueDef(
    5889          80 :         papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
    5890          40 :     m_bGridCellEncodingAsCO =
    5891          40 :         CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
    5892          80 :     CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
    5893             :     CPLString osFieldName(
    5894          80 :         CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
    5895             :     CPLString osQuantityDefinition(
    5896          80 :         CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
    5897             : 
    5898         121 :     pszSQL = sqlite3_mprintf(
    5899             :         "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
    5900             :         "(tile_matrix_set_name, datatype, scale, offset, precision, "
    5901             :         "grid_cell_encoding, uom, field_name, quantity_definition) "
    5902             :         "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
    5903             :         m_osRasterTable.c_str(),
    5904          40 :         (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
    5905             :         m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
    5906          41 :         osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
    5907             :         osQuantityDefinition.c_str());
    5908          40 :     m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
    5909          40 :     sqlite3_free(pszSQL);
    5910             : 
    5911             :     // Requirement 3 /gpkg-spatial-ref-sys-row
    5912             :     auto oResultTable = SQLQuery(
    5913          80 :         hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
    5914          40 :     bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
    5915          40 :     if (!bHasEPSG4979)
    5916             :     {
    5917          41 :         if (!m_bHasDefinition12_063 &&
    5918           1 :             !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
    5919             :         {
    5920           0 :             return false;
    5921             :         }
    5922             : 
    5923             :         // This is WKT 2...
    5924          40 :         const char *pszWKT =
    5925             :             "GEODCRS[\"WGS 84\","
    5926             :             "DATUM[\"World Geodetic System 1984\","
    5927             :             "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
    5928             :             "LENGTHUNIT[\"metre\",1.0]]],"
    5929             :             "CS[ellipsoidal,3],"
    5930             :             "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
    5931             :             "0.0174532925199433]],"
    5932             :             "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
    5933             :             "0.0174532925199433]],"
    5934             :             "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
    5935             :             "LENGTHUNIT[\"metre\",1.0]],"
    5936             :             "ID[\"EPSG\",4979]]";
    5937             : 
    5938          40 :         pszSQL = sqlite3_mprintf(
    5939             :             "INSERT INTO gpkg_spatial_ref_sys "
    5940             :             "(srs_name,srs_id,organization,organization_coordsys_id,"
    5941             :             "definition,definition_12_063) VALUES "
    5942             :             "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
    5943             :             pszWKT);
    5944          40 :         osSQL += ";";
    5945          40 :         osSQL += pszSQL;
    5946          40 :         sqlite3_free(pszSQL);
    5947             :     }
    5948             : 
    5949          40 :     return SQLCommand(hDB, osSQL) == OGRERR_NONE;
    5950             : }
    5951             : 
    5952             : /************************************************************************/
    5953             : /*                    HasGriddedCoverageAncillaryTable()                */
    5954             : /************************************************************************/
    5955             : 
    5956          44 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
    5957             : {
    5958             :     auto oResultTable = SQLQuery(
    5959             :         hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
    5960          44 :              "name = 'gpkg_2d_gridded_coverage_ancillary'");
    5961          44 :     bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
    5962          88 :     return bHasTable;
    5963             : }
    5964             : 
    5965             : /************************************************************************/
    5966             : /*                      GetUnderlyingDataset()                          */
    5967             : /************************************************************************/
    5968             : 
    5969           3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
    5970             : {
    5971           3 :     if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
    5972             :     {
    5973           0 :         auto poTmpDS = poVRTDS->GetSingleSimpleSource();
    5974           0 :         if (poTmpDS)
    5975           0 :             return poTmpDS;
    5976             :     }
    5977             : 
    5978           3 :     return poSrcDS;
    5979             : }
    5980             : 
    5981             : /************************************************************************/
    5982             : /*                            CreateCopy()                              */
    5983             : /************************************************************************/
    5984             : 
    5985             : typedef struct
    5986             : {
    5987             :     const char *pszName;
    5988             :     GDALResampleAlg eResampleAlg;
    5989             : } WarpResamplingAlg;
    5990             : 
    5991             : static const WarpResamplingAlg asResamplingAlg[] = {
    5992             :     {"NEAREST", GRA_NearestNeighbour},
    5993             :     {"BILINEAR", GRA_Bilinear},
    5994             :     {"CUBIC", GRA_Cubic},
    5995             :     {"CUBICSPLINE", GRA_CubicSpline},
    5996             :     {"LANCZOS", GRA_Lanczos},
    5997             :     {"MODE", GRA_Mode},
    5998             :     {"AVERAGE", GRA_Average},
    5999             :     {"RMS", GRA_RMS},
    6000             : };
    6001             : 
    6002         141 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    6003             :                                                GDALDataset *poSrcDS,
    6004             :                                                int bStrict, char **papszOptions,
    6005             :                                                GDALProgressFunc pfnProgress,
    6006             :                                                void *pProgressData)
    6007             : {
    6008         141 :     const int nBands = poSrcDS->GetRasterCount();
    6009         141 :     if (nBands == 0)
    6010             :     {
    6011           2 :         GDALDataset *poDS = nullptr;
    6012             :         GDALDriver *poThisDriver =
    6013           2 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    6014           2 :         if (poThisDriver != nullptr)
    6015             :         {
    6016           2 :             poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
    6017             :                                                    bStrict, papszOptions,
    6018             :                                                    pfnProgress, pProgressData);
    6019             :         }
    6020           2 :         return poDS;
    6021             :     }
    6022             : 
    6023             :     const char *pszTilingScheme =
    6024         139 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    6025             : 
    6026         278 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    6027         139 :     if (CPLTestBool(
    6028         144 :             CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
    6029           5 :         CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
    6030             :     {
    6031             :         CPLString osBasename(
    6032           6 :             CPLGetBasename(GetUnderlyingDataset(poSrcDS)->GetDescription()));
    6033           3 :         apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename);
    6034             :     }
    6035             : 
    6036         139 :     if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
    6037             :     {
    6038           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6039             :                  "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
    6040             :                  "4 (RGBA) band dataset supported");
    6041           1 :         return nullptr;
    6042             :     }
    6043             : 
    6044         138 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    6045         276 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    6046         138 :         !EQUAL(pszUnitType, ""))
    6047             :     {
    6048           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    6049             :     }
    6050             : 
    6051         138 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    6052             :     {
    6053         114 :         if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    6054             :         {
    6055           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6056             :                      "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    6057           0 :             return nullptr;
    6058             :         }
    6059             : 
    6060         114 :         GDALGeoPackageDataset *poDS = nullptr;
    6061             :         GDALDriver *poThisDriver =
    6062         114 :             GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
    6063         114 :         if (poThisDriver != nullptr)
    6064             :         {
    6065         114 :             apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
    6066         114 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    6067             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    6068             :                                                 apszUpdatedOptions, pfnProgress,
    6069         114 :                                                 pProgressData));
    6070             : 
    6071         211 :             if (poDS != nullptr &&
    6072         114 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
    6073             :                 nBands <= 3)
    6074             :             {
    6075          61 :                 poDS->m_nBandCountFromMetadata = nBands;
    6076          61 :                 poDS->m_bMetadataDirty = true;
    6077             :             }
    6078             :         }
    6079         114 :         if (poDS)
    6080          97 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6081         114 :         return poDS;
    6082             :     }
    6083             : 
    6084          48 :     const auto poTS = GetTilingScheme(pszTilingScheme);
    6085          24 :     if (!poTS)
    6086             :     {
    6087           2 :         return nullptr;
    6088             :     }
    6089          22 :     const int nEPSGCode = poTS->nEPSGCode;
    6090             : 
    6091          44 :     OGRSpatialReference oSRS;
    6092          22 :     if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
    6093             :     {
    6094           0 :         return nullptr;
    6095             :     }
    6096          22 :     char *pszWKT = nullptr;
    6097          22 :     oSRS.exportToWkt(&pszWKT);
    6098          22 :     char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
    6099             : 
    6100          22 :     void *hTransformArg = nullptr;
    6101             : 
    6102             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    6103             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    6104             :     // EPSG:3857.
    6105             :     double adfSrcGeoTransform[6];
    6106          22 :     std::unique_ptr<GDALDataset> poTmpDS;
    6107          22 :     bool bEPSG3857Adjust = false;
    6108          30 :     if (nEPSGCode == 3857 &&
    6109           8 :         poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
    6110          38 :         adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
    6111           8 :         adfSrcGeoTransform[5] < 0)
    6112             :     {
    6113           8 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    6114           8 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    6115             :         {
    6116           2 :             double maxLat = adfSrcGeoTransform[3];
    6117           2 :             double minLat = adfSrcGeoTransform[3] +
    6118           2 :                             poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
    6119             :             // Corresponds to the latitude of below MAX_GM
    6120           2 :             constexpr double MAX_LAT = 85.0511287798066;
    6121           2 :             bool bModified = false;
    6122           2 :             if (maxLat > MAX_LAT)
    6123             :             {
    6124           2 :                 maxLat = MAX_LAT;
    6125           2 :                 bModified = true;
    6126             :             }
    6127           2 :             if (minLat < -MAX_LAT)
    6128             :             {
    6129           2 :                 minLat = -MAX_LAT;
    6130           2 :                 bModified = true;
    6131             :             }
    6132           2 :             if (bModified)
    6133             :             {
    6134           4 :                 CPLStringList aosOptions;
    6135           2 :                 aosOptions.AddString("-of");
    6136           2 :                 aosOptions.AddString("VRT");
    6137           2 :                 aosOptions.AddString("-projwin");
    6138             :                 aosOptions.AddString(
    6139           2 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
    6140           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
    6141             :                 aosOptions.AddString(
    6142           2 :                     CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
    6143           2 :                                             poSrcDS->GetRasterXSize() *
    6144           2 :                                                 adfSrcGeoTransform[1]));
    6145           2 :                 aosOptions.AddString(CPLSPrintf("%.17g", minLat));
    6146             :                 auto psOptions =
    6147           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    6148           2 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    6149             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    6150           2 :                 GDALTranslateOptionsFree(psOptions);
    6151           2 :                 if (poTmpDS)
    6152             :                 {
    6153           2 :                     bEPSG3857Adjust = true;
    6154           2 :                     hTransformArg = GDALCreateGenImgProjTransformer2(
    6155           2 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    6156             :                         papszTO);
    6157             :                 }
    6158             :             }
    6159             :         }
    6160             :     }
    6161          22 :     if (hTransformArg == nullptr)
    6162             :     {
    6163             :         hTransformArg =
    6164          20 :             GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
    6165             :     }
    6166             : 
    6167          22 :     if (hTransformArg == nullptr)
    6168             :     {
    6169           1 :         CPLFree(pszWKT);
    6170           1 :         CSLDestroy(papszTO);
    6171           1 :         return nullptr;
    6172             :     }
    6173             : 
    6174          21 :     GDALTransformerInfo *psInfo =
    6175             :         static_cast<GDALTransformerInfo *>(hTransformArg);
    6176             :     double adfGeoTransform[6];
    6177             :     double adfExtent[4];
    6178             :     int nXSize, nYSize;
    6179             : 
    6180          21 :     if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
    6181             :                                  adfGeoTransform, &nXSize, &nYSize, adfExtent,
    6182          21 :                                  0) != CE_None)
    6183             :     {
    6184           0 :         CPLFree(pszWKT);
    6185           0 :         CSLDestroy(papszTO);
    6186           0 :         GDALDestroyGenImgProjTransformer(hTransformArg);
    6187           0 :         return nullptr;
    6188             :     }
    6189             : 
    6190          21 :     GDALDestroyGenImgProjTransformer(hTransformArg);
    6191          21 :     hTransformArg = nullptr;
    6192          21 :     poTmpDS.reset();
    6193             : 
    6194          21 :     if (bEPSG3857Adjust)
    6195             :     {
    6196           2 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    6197           2 :         constexpr double MAX_GM =
    6198             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    6199           2 :         double maxNorthing = adfGeoTransform[3];
    6200           2 :         double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
    6201           2 :         bool bChanged = false;
    6202           2 :         if (maxNorthing > MAX_GM)
    6203             :         {
    6204           2 :             bChanged = true;
    6205           2 :             maxNorthing = MAX_GM;
    6206             :         }
    6207           2 :         if (minNorthing < -MAX_GM)
    6208             :         {
    6209           2 :             bChanged = true;
    6210           2 :             minNorthing = -MAX_GM;
    6211             :         }
    6212           2 :         if (bChanged)
    6213             :         {
    6214           2 :             adfGeoTransform[3] = maxNorthing;
    6215           2 :             nYSize =
    6216           2 :                 int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
    6217           2 :             adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
    6218           2 :             adfExtent[3] = maxNorthing;
    6219             :         }
    6220             :     }
    6221             : 
    6222          21 :     double dfComputedRes = adfGeoTransform[1];
    6223          21 :     double dfPrevRes = 0.0;
    6224          21 :     double dfRes = 0.0;
    6225          21 :     int nZoomLevel = 0;  // Used after for.
    6226          21 :     const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    6227          21 :     if (pszZoomLevel)
    6228             :     {
    6229           2 :         nZoomLevel = atoi(pszZoomLevel);
    6230             : 
    6231           2 :         int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    6232           2 :         while ((1 << nMaxZoomLevelForThisTM) >
    6233           4 :                    INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6234           2 :                (1 << nMaxZoomLevelForThisTM) >
    6235           2 :                    INT_MAX / poTS->nTileYCountZoomLevel0)
    6236             :         {
    6237           0 :             --nMaxZoomLevelForThisTM;
    6238             :         }
    6239             : 
    6240           2 :         if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
    6241             :         {
    6242           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6243             :                      "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
    6244             :                      pszZoomLevel, nMaxZoomLevelForThisTM);
    6245           1 :             CPLFree(pszWKT);
    6246           1 :             CSLDestroy(papszTO);
    6247           1 :             return nullptr;
    6248             :         }
    6249             :     }
    6250             :     else
    6251             :     {
    6252         171 :         for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
    6253             :         {
    6254         171 :             dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6255         171 :             if (dfComputedRes > dfRes ||
    6256         152 :                 fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
    6257             :                 break;
    6258         152 :             dfPrevRes = dfRes;
    6259             :         }
    6260          38 :         if (nZoomLevel == MAX_ZOOM_LEVEL ||
    6261          38 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6262          19 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
    6263             :         {
    6264           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6265             :                      "Could not find an appropriate zoom level");
    6266           0 :             CPLFree(pszWKT);
    6267           0 :             CSLDestroy(papszTO);
    6268           0 :             return nullptr;
    6269             :         }
    6270             : 
    6271          19 :         if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
    6272             :         {
    6273          17 :             const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
    6274             :                 papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
    6275          17 :             if (EQUAL(pszZoomLevelStrategy, "LOWER"))
    6276             :             {
    6277           1 :                 nZoomLevel--;
    6278             :             }
    6279          16 :             else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
    6280             :             {
    6281             :                 /* do nothing */
    6282             :             }
    6283             :             else
    6284             :             {
    6285          15 :                 if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    6286          13 :                     nZoomLevel--;
    6287             :             }
    6288             :         }
    6289             :     }
    6290             : 
    6291          20 :     dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6292             : 
    6293          20 :     double dfMinX = adfExtent[0];
    6294          20 :     double dfMinY = adfExtent[1];
    6295          20 :     double dfMaxX = adfExtent[2];
    6296          20 :     double dfMaxY = adfExtent[3];
    6297             : 
    6298          20 :     nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
    6299          20 :     nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
    6300          20 :     adfGeoTransform[1] = dfRes;
    6301          20 :     adfGeoTransform[5] = -dfRes;
    6302             : 
    6303          20 :     const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    6304          20 :     int nTargetBands = nBands;
    6305             :     /* For grey level or RGB, if there's reprojection involved, add an alpha */
    6306             :     /* channel */
    6307          37 :     if (eDT == GDT_Byte &&
    6308          13 :         ((nBands == 1 &&
    6309          17 :           poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
    6310             :          nBands == 3))
    6311             :     {
    6312          30 :         OGRSpatialReference oSrcSRS;
    6313          15 :         oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
    6314          15 :         oSrcSRS.AutoIdentifyEPSG();
    6315          30 :         if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
    6316          15 :             atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
    6317             :         {
    6318          13 :             nTargetBands++;
    6319             :         }
    6320             :     }
    6321             : 
    6322          20 :     GDALResampleAlg eResampleAlg = GRA_Bilinear;
    6323          20 :     const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
    6324          20 :     if (pszResampling)
    6325             :     {
    6326           6 :         for (size_t iAlg = 0;
    6327           6 :              iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
    6328             :              iAlg++)
    6329             :         {
    6330           6 :             if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
    6331             :             {
    6332           3 :                 eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
    6333           3 :                 break;
    6334             :             }
    6335             :         }
    6336             :     }
    6337             : 
    6338          16 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    6339          36 :         eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
    6340             :     {
    6341           0 :         CPLError(
    6342             :             CE_Warning, CPLE_AppDefined,
    6343             :             "Input dataset has a color table, which will likely lead to "
    6344             :             "bad results when using a resampling method other than "
    6345             :             "nearest neighbour or mode. Converting the dataset to 24/32 bit "
    6346             :             "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
    6347             :     }
    6348             : 
    6349          20 :     GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
    6350          20 :     if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
    6351             :                        apszUpdatedOptions)))
    6352             :     {
    6353           1 :         delete poDS;
    6354           1 :         CPLFree(pszWKT);
    6355           1 :         CSLDestroy(papszTO);
    6356           1 :         return nullptr;
    6357             :     }
    6358             : 
    6359             :     // Assign nodata values before the SetGeoTransform call.
    6360             :     // SetGeoTransform will trigger creation of the overview datasets for each
    6361             :     // zoom level and at that point the nodata value needs to be known.
    6362          19 :     int bHasNoData = FALSE;
    6363             :     double dfNoDataValue =
    6364          19 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    6365          19 :     if (eDT != GDT_Byte && bHasNoData)
    6366             :     {
    6367           3 :         poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
    6368             :     }
    6369             : 
    6370          19 :     poDS->SetGeoTransform(adfGeoTransform);
    6371          19 :     poDS->SetProjection(pszWKT);
    6372          19 :     CPLFree(pszWKT);
    6373          19 :     pszWKT = nullptr;
    6374          24 :     if (nTargetBands == 1 && nBands == 1 &&
    6375           5 :         poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    6376             :     {
    6377           2 :         poDS->GetRasterBand(1)->SetColorTable(
    6378           1 :             poSrcDS->GetRasterBand(1)->GetColorTable());
    6379             :     }
    6380             : 
    6381          19 :     hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
    6382          19 :     CSLDestroy(papszTO);
    6383          19 :     if (hTransformArg == nullptr)
    6384             :     {
    6385           0 :         delete poDS;
    6386           0 :         return nullptr;
    6387             :     }
    6388             : 
    6389          19 :     poDS->SetMetadata(poSrcDS->GetMetadata());
    6390             : 
    6391             :     /* -------------------------------------------------------------------- */
    6392             :     /*      Warp the transformer with a linear approximator                 */
    6393             :     /* -------------------------------------------------------------------- */
    6394          19 :     hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
    6395             :                                                 hTransformArg, 0.125);
    6396          19 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
    6397             : 
    6398             :     /* -------------------------------------------------------------------- */
    6399             :     /*      Setup warp options.                                             */
    6400             :     /* -------------------------------------------------------------------- */
    6401          19 :     GDALWarpOptions *psWO = GDALCreateWarpOptions();
    6402             : 
    6403          19 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    6404          19 :     psWO->papszWarpOptions =
    6405          19 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    6406          19 :     if (bHasNoData)
    6407             :     {
    6408           3 :         if (dfNoDataValue == 0.0)
    6409             :         {
    6410             :             // Do not initialize in the case where nodata != 0, since we
    6411             :             // want the GeoPackage driver to return empty tiles at the nodata
    6412             :             // value instead of 0 as GDAL core would
    6413           0 :             psWO->papszWarpOptions =
    6414           0 :                 CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
    6415             :         }
    6416             : 
    6417           3 :         psWO->padfSrcNoDataReal =
    6418           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6419           3 :         psWO->padfSrcNoDataReal[0] = dfNoDataValue;
    6420             : 
    6421           3 :         psWO->padfDstNoDataReal =
    6422           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6423           3 :         psWO->padfDstNoDataReal[0] = dfNoDataValue;
    6424             :     }
    6425          19 :     psWO->eWorkingDataType = eDT;
    6426          19 :     psWO->eResampleAlg = eResampleAlg;
    6427             : 
    6428          19 :     psWO->hSrcDS = poSrcDS;
    6429          19 :     psWO->hDstDS = poDS;
    6430             : 
    6431          19 :     psWO->pfnTransformer = GDALApproxTransform;
    6432          19 :     psWO->pTransformerArg = hTransformArg;
    6433             : 
    6434          19 :     psWO->pfnProgress = pfnProgress;
    6435          19 :     psWO->pProgressArg = pProgressData;
    6436             : 
    6437             :     /* -------------------------------------------------------------------- */
    6438             :     /*      Setup band mapping.                                             */
    6439             :     /* -------------------------------------------------------------------- */
    6440             : 
    6441          19 :     if (nBands == 2 || nBands == 4)
    6442           1 :         psWO->nBandCount = nBands - 1;
    6443             :     else
    6444          18 :         psWO->nBandCount = nBands;
    6445             : 
    6446          19 :     psWO->panSrcBands =
    6447          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6448          19 :     psWO->panDstBands =
    6449          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6450             : 
    6451          46 :     for (int i = 0; i < psWO->nBandCount; i++)
    6452             :     {
    6453          27 :         psWO->panSrcBands[i] = i + 1;
    6454          27 :         psWO->panDstBands[i] = i + 1;
    6455             :     }
    6456             : 
    6457          19 :     if (nBands == 2 || nBands == 4)
    6458             :     {
    6459           1 :         psWO->nSrcAlphaBand = nBands;
    6460             :     }
    6461          19 :     if (nTargetBands == 2 || nTargetBands == 4)
    6462             :     {
    6463          13 :         psWO->nDstAlphaBand = nTargetBands;
    6464             :     }
    6465             : 
    6466             :     /* -------------------------------------------------------------------- */
    6467             :     /*      Initialize and execute the warp.                                */
    6468             :     /* -------------------------------------------------------------------- */
    6469          19 :     GDALWarpOperation oWO;
    6470             : 
    6471          19 :     CPLErr eErr = oWO.Initialize(psWO);
    6472          19 :     if (eErr == CE_None)
    6473             :     {
    6474             :         /*if( bMulti )
    6475             :             eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
    6476             :         else*/
    6477          19 :         eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
    6478             :     }
    6479          19 :     if (eErr != CE_None)
    6480             :     {
    6481           0 :         delete poDS;
    6482           0 :         poDS = nullptr;
    6483             :     }
    6484             : 
    6485          19 :     GDALDestroyTransformer(hTransformArg);
    6486          19 :     GDALDestroyWarpOptions(psWO);
    6487             : 
    6488          19 :     if (poDS)
    6489          19 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6490             : 
    6491          19 :     return poDS;
    6492             : }
    6493             : 
    6494             : /************************************************************************/
    6495             : /*                        ParseCompressionOptions()                     */
    6496             : /************************************************************************/
    6497             : 
    6498         424 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
    6499             : {
    6500         424 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6501         424 :     if (pszZLevel)
    6502           0 :         m_nZLevel = atoi(pszZLevel);
    6503             : 
    6504         424 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6505         424 :     if (pszQuality)
    6506           0 :         m_nQuality = atoi(pszQuality);
    6507             : 
    6508         424 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6509         424 :     if (pszDither)
    6510           0 :         m_bDither = CPLTestBool(pszDither);
    6511         424 : }
    6512             : 
    6513             : /************************************************************************/
    6514             : /*                          RegisterWebPExtension()                     */
    6515             : /************************************************************************/
    6516             : 
    6517          11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
    6518             : {
    6519          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6520           0 :         return false;
    6521             : 
    6522          11 :     char *pszSQL = sqlite3_mprintf(
    6523             :         "INSERT INTO gpkg_extensions "
    6524             :         "(table_name, column_name, extension_name, definition, scope) "
    6525             :         "VALUES "
    6526             :         "('%q', 'tile_data', 'gpkg_webp', "
    6527             :         "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
    6528             :         "'read-write')",
    6529             :         m_osRasterTable.c_str());
    6530          11 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6531          11 :     sqlite3_free(pszSQL);
    6532             : 
    6533          11 :     return OGRERR_NONE == eErr;
    6534             : }
    6535             : 
    6536             : /************************************************************************/
    6537             : /*                       RegisterZoomOtherExtension()                   */
    6538             : /************************************************************************/
    6539             : 
    6540           1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
    6541             : {
    6542           1 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6543           0 :         return false;
    6544             : 
    6545           1 :     char *pszSQL = sqlite3_mprintf(
    6546             :         "INSERT INTO gpkg_extensions "
    6547             :         "(table_name, column_name, extension_name, definition, scope) "
    6548             :         "VALUES "
    6549             :         "('%q', 'tile_data', 'gpkg_zoom_other', "
    6550             :         "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
    6551             :         "'read-write')",
    6552             :         m_osRasterTable.c_str());
    6553           1 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6554           1 :     sqlite3_free(pszSQL);
    6555           1 :     return OGRERR_NONE == eErr;
    6556             : }
    6557             : 
    6558             : /************************************************************************/
    6559             : /*                              GetLayer()                              */
    6560             : /************************************************************************/
    6561             : 
    6562       13429 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
    6563             : 
    6564             : {
    6565       13429 :     if (iLayer < 0 || iLayer >= m_nLayers)
    6566           6 :         return nullptr;
    6567             :     else
    6568       13423 :         return m_papoLayers[iLayer];
    6569             : }
    6570             : 
    6571             : /************************************************************************/
    6572             : /*                           LaunderName()                              */
    6573             : /************************************************************************/
    6574             : 
    6575             : /** Launder identifiers (table, column names) according to guidance at
    6576             :  * https://www.geopackage.org/guidance/getting-started.html:
    6577             :  * "For maximum interoperability, start your database identifiers (table names,
    6578             :  * column names, etc.) with a lowercase character and only use lowercase
    6579             :  * characters, numbers 0-9, and underscores (_)."
    6580             :  */
    6581             : 
    6582             : /* static */
    6583           5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
    6584             : {
    6585           5 :     char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
    6586          10 :     const std::string osStrASCII(pszASCII);
    6587           5 :     CPLFree(pszASCII);
    6588             : 
    6589          10 :     std::string osRet;
    6590           5 :     osRet.reserve(osStrASCII.size());
    6591             : 
    6592          29 :     for (size_t i = 0; i < osStrASCII.size(); ++i)
    6593             :     {
    6594          24 :         if (osRet.empty())
    6595             :         {
    6596           5 :             if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6597             :             {
    6598           2 :                 osRet += (osStrASCII[i] - 'A' + 'a');
    6599             :             }
    6600           3 :             else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
    6601             :             {
    6602           2 :                 osRet += osStrASCII[i];
    6603             :             }
    6604             :             else
    6605             :             {
    6606           1 :                 continue;
    6607             :             }
    6608             :         }
    6609          19 :         else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6610             :         {
    6611          11 :             osRet += (osStrASCII[i] - 'A' + 'a');
    6612             :         }
    6613           9 :         else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
    6614          14 :                  (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
    6615           5 :                  osStrASCII[i] == '_')
    6616             :         {
    6617           7 :             osRet += osStrASCII[i];
    6618             :         }
    6619             :         else
    6620             :         {
    6621           1 :             osRet += '_';
    6622             :         }
    6623             :     }
    6624             : 
    6625           5 :     if (osRet.empty() && !osStrASCII.empty())
    6626           2 :         return LaunderName(std::string("x").append(osStrASCII));
    6627             : 
    6628           4 :     if (osRet != osStr)
    6629             :     {
    6630           3 :         CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
    6631             :                  osRet.c_str());
    6632             :     }
    6633             : 
    6634           4 :     return osRet;
    6635             : }
    6636             : 
    6637             : /************************************************************************/
    6638             : /*                          ICreateLayer()                              */
    6639             : /************************************************************************/
    6640             : 
    6641             : OGRLayer *
    6642         650 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6643             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6644             :                                     CSLConstList papszOptions)
    6645             : {
    6646             :     /* -------------------------------------------------------------------- */
    6647             :     /*      Verify we are in update mode.                                   */
    6648             :     /* -------------------------------------------------------------------- */
    6649         650 :     if (!GetUpdate())
    6650             :     {
    6651           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    6652             :                  "Data source %s opened read-only.\n"
    6653             :                  "New layer %s cannot be created.\n",
    6654             :                  m_pszFilename, pszLayerName);
    6655             : 
    6656           0 :         return nullptr;
    6657             :     }
    6658             : 
    6659             :     const bool bLaunder =
    6660         650 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6661             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6662        1950 :                                            : std::string(pszLayerName));
    6663             : 
    6664             :     const auto eGType =
    6665         650 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6666             :     const auto poSpatialRef =
    6667         650 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6668             : 
    6669         650 :     if (!m_bHasGPKGGeometryColumns)
    6670             :     {
    6671           1 :         if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
    6672             :         {
    6673           0 :             return nullptr;
    6674             :         }
    6675           1 :         m_bHasGPKGGeometryColumns = true;
    6676             :     }
    6677             : 
    6678             :     // Check identifier unicity
    6679         650 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6680         650 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6681           0 :         pszIdentifier = nullptr;
    6682         650 :     if (pszIdentifier != nullptr)
    6683             :     {
    6684          13 :         for (int i = 0; i < m_nLayers; ++i)
    6685             :         {
    6686             :             const char *pszOtherIdentifier =
    6687           9 :                 m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    6688           9 :             if (pszOtherIdentifier == nullptr)
    6689           6 :                 pszOtherIdentifier = m_papoLayers[i]->GetName();
    6690          18 :             if (pszOtherIdentifier != nullptr &&
    6691          12 :                 EQUAL(pszOtherIdentifier, pszIdentifier) &&
    6692           3 :                 !EQUAL(m_papoLayers[i]->GetName(), osTableName.c_str()))
    6693             :             {
    6694           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6695             :                          "Identifier %s is already used by table %s",
    6696           2 :                          pszIdentifier, m_papoLayers[i]->GetName());
    6697           3 :                 return nullptr;
    6698             :             }
    6699             :         }
    6700             : 
    6701             :         // In case there would be table in gpkg_contents not listed as a
    6702             :         // vector layer
    6703           4 :         char *pszSQL = sqlite3_mprintf(
    6704             :             "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
    6705             :             "LIMIT 2",
    6706             :             pszIdentifier);
    6707           4 :         auto oResult = SQLQuery(hDB, pszSQL);
    6708           4 :         sqlite3_free(pszSQL);
    6709           8 :         if (oResult && oResult->RowCount() > 0 &&
    6710           9 :             oResult->GetValue(0, 0) != nullptr &&
    6711           1 :             !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
    6712             :         {
    6713           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6714             :                      "Identifier %s is already used by table %s", pszIdentifier,
    6715             :                      oResult->GetValue(0, 0));
    6716           1 :             return nullptr;
    6717             :         }
    6718             :     }
    6719             : 
    6720             :     /* Read GEOMETRY_NAME option */
    6721             :     const char *pszGeomColumnName =
    6722         647 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6723         647 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6724         611 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6725         647 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6726             :     {
    6727         565 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6728         565 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6729         561 :             pszGeomColumnName = nullptr;
    6730             :     }
    6731         647 :     if (pszGeomColumnName == nullptr)
    6732         607 :         pszGeomColumnName = "geom";
    6733             :     const bool bGeomNullable =
    6734         647 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6735             : 
    6736             :     /* Read FID option */
    6737         647 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6738         647 :     if (pszFIDColumnName == nullptr)
    6739         623 :         pszFIDColumnName = "fid";
    6740             : 
    6741         647 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6742             :     {
    6743         647 :         if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
    6744             :         {
    6745           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6746             :                      "The primary key (%s) name may not contain special "
    6747             :                      "characters or spaces",
    6748             :                      pszFIDColumnName);
    6749           0 :             return nullptr;
    6750             :         }
    6751             : 
    6752             :         /* Avoiding gpkg prefixes is not an official requirement, but seems wise
    6753             :          */
    6754         647 :         if (STARTS_WITH(osTableName.c_str(), "gpkg"))
    6755             :         {
    6756           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6757             :                      "The layer name may not begin with 'gpkg' as it is a "
    6758             :                      "reserved geopackage prefix");
    6759           0 :             return nullptr;
    6760             :         }
    6761             : 
    6762             :         /* Preemptively try and avoid sqlite3 syntax errors due to  */
    6763             :         /* illegal characters. */
    6764         647 :         if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
    6765             :             0)
    6766             :         {
    6767           0 :             CPLError(
    6768             :                 CE_Failure, CPLE_AppDefined,
    6769             :                 "The layer name may not contain special characters or spaces");
    6770           0 :             return nullptr;
    6771             :         }
    6772             :     }
    6773             : 
    6774             :     /* Check for any existing layers that already use this name */
    6775         836 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    6776             :     {
    6777         190 :         if (EQUAL(osTableName.c_str(), m_papoLayers[iLayer]->GetName()))
    6778             :         {
    6779             :             const char *pszOverwrite =
    6780           2 :                 CSLFetchNameValue(papszOptions, "OVERWRITE");
    6781           2 :             if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
    6782             :             {
    6783           1 :                 DeleteLayer(iLayer);
    6784             :             }
    6785             :             else
    6786             :             {
    6787           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6788             :                          "Layer %s already exists, CreateLayer failed.\n"
    6789             :                          "Use the layer creation option OVERWRITE=YES to "
    6790             :                          "replace it.",
    6791             :                          osTableName.c_str());
    6792           1 :                 return nullptr;
    6793             :             }
    6794             :         }
    6795             :     }
    6796             : 
    6797         646 :     if (m_nLayers == 1)
    6798             :     {
    6799             :         // Async RTree building doesn't play well with multiple layer:
    6800             :         // SQLite3 locks being hold for a long time, random failed commits,
    6801             :         // etc.
    6802          65 :         m_papoLayers[0]->FinishOrDisableThreadedRTree();
    6803             :     }
    6804             : 
    6805             :     /* Create a blank layer. */
    6806             :     auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
    6807        1292 :         new OGRGeoPackageTableLayer(this, osTableName.c_str()));
    6808             : 
    6809         646 :     OGRSpatialReference *poSRS = nullptr;
    6810         646 :     if (poSpatialRef)
    6811             :     {
    6812         201 :         poSRS = poSpatialRef->Clone();
    6813         201 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6814             :     }
    6815        1293 :     poLayer->SetCreationParameters(
    6816             :         eGType,
    6817         647 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6818             :         bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
    6819        1292 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6820             :                            : OGRGeomCoordinatePrecision(),
    6821         646 :         CPLTestBool(
    6822             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6823         646 :         CPLTestBool(CSLFetchNameValueDef(
    6824             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6825         647 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6826             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6827         646 :     if (poSRS)
    6828             :     {
    6829         201 :         poSRS->Release();
    6830             :     }
    6831             : 
    6832         646 :     poLayer->SetLaunder(bLaunder);
    6833             : 
    6834             :     /* Should we create a spatial index ? */
    6835         646 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6836         646 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6837         646 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6838             :     {
    6839         578 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6840             :     }
    6841             : 
    6842         646 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6843         646 :     poLayer->SetTruncateFieldsFlag(
    6844         646 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6845         646 :     if (eGType == wkbNone)
    6846             :     {
    6847          46 :         const char *pszASpatialVariant = CSLFetchNameValueDef(
    6848             :             papszOptions, "ASPATIAL_VARIANT",
    6849          46 :             m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
    6850             :                 ? "NOT_REGISTERED"
    6851             :                 : "GPKG_ATTRIBUTES");
    6852          46 :         GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
    6853          46 :         if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
    6854          34 :             eASpatialVariant = GPKG_ATTRIBUTES;
    6855          12 :         else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
    6856             :         {
    6857           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6858             :                      "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
    6859           0 :             return nullptr;
    6860             :         }
    6861          12 :         else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
    6862          12 :             eASpatialVariant = NOT_REGISTERED;
    6863             :         else
    6864             :         {
    6865           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6866             :                      "Unsupported value for ASPATIAL_VARIANT: %s",
    6867             :                      pszASpatialVariant);
    6868           0 :             return nullptr;
    6869             :         }
    6870          46 :         poLayer->SetASpatialVariant(eASpatialVariant);
    6871             :     }
    6872             : 
    6873             :     const char *pszDateTimePrecision =
    6874         646 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6875         646 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6876             :     {
    6877           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6878             :     }
    6879         644 :     else if (EQUAL(pszDateTimePrecision, "SECOND"))
    6880             :     {
    6881           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6882           0 :             CPLError(
    6883             :                 CE_Warning, CPLE_AppDefined,
    6884             :                 "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
    6885           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
    6886             :     }
    6887         643 :     else if (EQUAL(pszDateTimePrecision, "MINUTE"))
    6888             :     {
    6889           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6890           0 :             CPLError(
    6891             :                 CE_Warning, CPLE_AppDefined,
    6892             :                 "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
    6893           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
    6894             :     }
    6895         642 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6896             :     {
    6897         641 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6898         630 :             poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6899             :     }
    6900             :     else
    6901             :     {
    6902           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6903             :                  "Unsupported value for DATETIME_PRECISION: %s",
    6904             :                  pszDateTimePrecision);
    6905           1 :         return nullptr;
    6906             :     }
    6907             : 
    6908             :     // If there was an ogr_empty_table table, we can remove it
    6909             :     // But do it at dataset closing, otherwise locking performance issues
    6910             :     // can arise (probably when transactions are used).
    6911         645 :     m_bRemoveOGREmptyTable = true;
    6912             : 
    6913        1290 :     m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
    6914         645 :         m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
    6915         645 :     auto poRet = poLayer.release();
    6916         645 :     m_papoLayers[m_nLayers] = poRet;
    6917         645 :     m_nLayers++;
    6918         645 :     return poRet;
    6919             : }
    6920             : 
    6921             : /************************************************************************/
    6922             : /*                          FindLayerIndex()                            */
    6923             : /************************************************************************/
    6924             : 
    6925          27 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
    6926             : 
    6927             : {
    6928          42 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    6929             :     {
    6930          28 :         if (EQUAL(pszLayerName, m_papoLayers[iLayer]->GetName()))
    6931          13 :             return iLayer;
    6932             :     }
    6933          14 :     return -1;
    6934             : }
    6935             : 
    6936             : /************************************************************************/
    6937             : /*                       DeleteLayerCommon()                            */
    6938             : /************************************************************************/
    6939             : 
    6940          36 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    6941             : {
    6942             :     // Temporary remove foreign key checks
    6943             :     const GPKGTemporaryForeignKeyCheckDisabler
    6944          36 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    6945             : 
    6946          36 :     char *pszSQL = sqlite3_mprintf(
    6947             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    6948             :         pszLayerName);
    6949          36 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    6950          36 :     sqlite3_free(pszSQL);
    6951             : 
    6952          36 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    6953             :     {
    6954          34 :         pszSQL = sqlite3_mprintf(
    6955             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    6956             :             pszLayerName);
    6957          34 :         eErr = SQLCommand(hDB, pszSQL);
    6958          34 :         sqlite3_free(pszSQL);
    6959             :     }
    6960             : 
    6961          36 :     if (eErr == OGRERR_NONE && HasMetadataTables())
    6962             :     {
    6963             :         // Delete from gpkg_metadata metadata records that are only referenced
    6964             :         // by the table we are about to drop
    6965           8 :         pszSQL = sqlite3_mprintf(
    6966             :             "DELETE FROM gpkg_metadata WHERE id IN ("
    6967             :             "SELECT DISTINCT md_file_id FROM "
    6968             :             "gpkg_metadata_reference WHERE "
    6969             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6970             :             "AND id NOT IN ("
    6971             :             "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
    6972             :             "md_file_id IN (SELECT DISTINCT md_file_id FROM "
    6973             :             "gpkg_metadata_reference WHERE "
    6974             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    6975             :             "AND lower(table_name) <> lower('%q'))",
    6976             :             pszLayerName, pszLayerName, pszLayerName);
    6977           8 :         eErr = SQLCommand(hDB, pszSQL);
    6978           8 :         sqlite3_free(pszSQL);
    6979             : 
    6980           8 :         if (eErr == OGRERR_NONE)
    6981             :         {
    6982             :             pszSQL =
    6983           8 :                 sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
    6984             :                                 "lower(table_name) = lower('%q')",
    6985             :                                 pszLayerName);
    6986           8 :             eErr = SQLCommand(hDB, pszSQL);
    6987           8 :             sqlite3_free(pszSQL);
    6988             :         }
    6989             :     }
    6990             : 
    6991          36 :     if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
    6992             :     {
    6993             :         // Remove reference to potential corresponding mapping table in
    6994             :         // gpkg_extensions
    6995           4 :         pszSQL = sqlite3_mprintf(
    6996             :             "DELETE FROM gpkg_extensions WHERE "
    6997             :             "extension_name IN ('related_tables', "
    6998             :             "'gpkg_related_tables') AND lower(table_name) = "
    6999             :             "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
    7000             :             "lower(base_table_name) = lower('%q') OR "
    7001             :             "lower(related_table_name) = lower('%q') OR "
    7002             :             "lower(mapping_table_name) = lower('%q'))",
    7003             :             pszLayerName, pszLayerName, pszLayerName);
    7004           4 :         eErr = SQLCommand(hDB, pszSQL);
    7005           4 :         sqlite3_free(pszSQL);
    7006             : 
    7007           4 :         if (eErr == OGRERR_NONE)
    7008             :         {
    7009             :             // Remove reference to potential corresponding mapping table in
    7010             :             // gpkgext_relations
    7011             :             pszSQL =
    7012           4 :                 sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
    7013             :                                 "lower(base_table_name) = lower('%q') OR "
    7014             :                                 "lower(related_table_name) = lower('%q') OR "
    7015             :                                 "lower(mapping_table_name) = lower('%q')",
    7016             :                                 pszLayerName, pszLayerName, pszLayerName);
    7017           4 :             eErr = SQLCommand(hDB, pszSQL);
    7018           4 :             sqlite3_free(pszSQL);
    7019             :         }
    7020             : 
    7021           4 :         if (eErr == OGRERR_NONE && HasExtensionsTable())
    7022             :         {
    7023             :             // If there is no longer any mapping table, then completely
    7024             :             // remove any reference to the extension in gpkg_extensions
    7025             :             // as mandated per the related table specification.
    7026             :             OGRErr err;
    7027           4 :             if (SQLGetInteger(hDB,
    7028             :                               "SELECT COUNT(*) FROM gpkg_extensions WHERE "
    7029             :                               "extension_name IN ('related_tables', "
    7030             :                               "'gpkg_related_tables') AND "
    7031             :                               "lower(table_name) != 'gpkgext_relations'",
    7032           4 :                               &err) == 0)
    7033             :             {
    7034           2 :                 eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
    7035             :                                        "extension_name IN ('related_tables', "
    7036             :                                        "'gpkg_related_tables')");
    7037             :             }
    7038             : 
    7039           4 :             ClearCachedRelationships();
    7040             :         }
    7041             :     }
    7042             : 
    7043          36 :     if (eErr == OGRERR_NONE)
    7044             :     {
    7045          36 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    7046          36 :         eErr = SQLCommand(hDB, pszSQL);
    7047          36 :         sqlite3_free(pszSQL);
    7048             :     }
    7049             : 
    7050             :     // Check foreign key integrity
    7051          36 :     if (eErr == OGRERR_NONE)
    7052             :     {
    7053          36 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7054             :     }
    7055             : 
    7056          72 :     return eErr;
    7057             : }
    7058             : 
    7059             : /************************************************************************/
    7060             : /*                            DeleteLayer()                             */
    7061             : /************************************************************************/
    7062             : 
    7063          33 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    7064             : {
    7065          33 :     if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
    7066           2 :         return OGRERR_FAILURE;
    7067             : 
    7068          31 :     m_papoLayers[iLayer]->ResetReading();
    7069          31 :     m_papoLayers[iLayer]->SyncToDisk();
    7070             : 
    7071          62 :     CPLString osLayerName = m_papoLayers[iLayer]->GetName();
    7072             : 
    7073          31 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    7074             : 
    7075             :     // Temporary remove foreign key checks
    7076             :     const GPKGTemporaryForeignKeyCheckDisabler
    7077          31 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7078             : 
    7079          31 :     OGRErr eErr = SoftStartTransaction();
    7080             : 
    7081          31 :     if (eErr == OGRERR_NONE)
    7082             :     {
    7083          31 :         if (m_papoLayers[iLayer]->HasSpatialIndex())
    7084          28 :             m_papoLayers[iLayer]->DropSpatialIndex();
    7085             : 
    7086             :         char *pszSQL =
    7087          31 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    7088             :                             "lower(table_name) = lower('%q')",
    7089             :                             osLayerName.c_str());
    7090          31 :         eErr = SQLCommand(hDB, pszSQL);
    7091          31 :         sqlite3_free(pszSQL);
    7092             :     }
    7093             : 
    7094          31 :     if (eErr == OGRERR_NONE && HasDataColumnsTable())
    7095             :     {
    7096           1 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
    7097             :                                        "lower(table_name) = lower('%q')",
    7098             :                                        osLayerName.c_str());
    7099           1 :         eErr = SQLCommand(hDB, pszSQL);
    7100           1 :         sqlite3_free(pszSQL);
    7101             :     }
    7102             : 
    7103             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7104          31 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    7105             :     {
    7106          31 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    7107             :                                        "lower(table_name) = lower('%q')",
    7108             :                                        osLayerName.c_str());
    7109          31 :         eErr = SQLCommand(hDB, pszSQL);
    7110          31 :         sqlite3_free(pszSQL);
    7111             :     }
    7112             : #endif
    7113             : 
    7114          31 :     if (eErr == OGRERR_NONE)
    7115             :     {
    7116          31 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    7117             :     }
    7118             : 
    7119          31 :     if (eErr == OGRERR_NONE)
    7120             :     {
    7121          31 :         eErr = SoftCommitTransaction();
    7122          31 :         if (eErr == OGRERR_NONE)
    7123             :         {
    7124             :             /* Delete the layer object and remove the gap in the layers list */
    7125          31 :             delete m_papoLayers[iLayer];
    7126          31 :             memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
    7127          31 :                     sizeof(void *) * (m_nLayers - iLayer - 1));
    7128          31 :             m_nLayers--;
    7129             :         }
    7130             :     }
    7131             :     else
    7132             :     {
    7133           0 :         SoftRollbackTransaction();
    7134             :     }
    7135             : 
    7136          31 :     return eErr;
    7137             : }
    7138             : 
    7139             : /************************************************************************/
    7140             : /*                       DeleteRasterLayer()                            */
    7141             : /************************************************************************/
    7142             : 
    7143           2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
    7144             : {
    7145             :     // Temporary remove foreign key checks
    7146             :     const GPKGTemporaryForeignKeyCheckDisabler
    7147           2 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7148             : 
    7149           2 :     OGRErr eErr = SoftStartTransaction();
    7150             : 
    7151           2 :     if (eErr == OGRERR_NONE)
    7152             :     {
    7153           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
    7154             :                                        "lower(table_name) = lower('%q')",
    7155             :                                        pszLayerName);
    7156           2 :         eErr = SQLCommand(hDB, pszSQL);
    7157           2 :         sqlite3_free(pszSQL);
    7158             :     }
    7159             : 
    7160           2 :     if (eErr == OGRERR_NONE)
    7161             :     {
    7162           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
    7163             :                                        "lower(table_name) = lower('%q')",
    7164             :                                        pszLayerName);
    7165           2 :         eErr = SQLCommand(hDB, pszSQL);
    7166           2 :         sqlite3_free(pszSQL);
    7167             :     }
    7168             : 
    7169           2 :     if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
    7170             :     {
    7171             :         char *pszSQL =
    7172           1 :             sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
    7173             :                             "WHERE lower(tile_matrix_set_name) = lower('%q')",
    7174             :                             pszLayerName);
    7175           1 :         eErr = SQLCommand(hDB, pszSQL);
    7176           1 :         sqlite3_free(pszSQL);
    7177             : 
    7178           1 :         if (eErr == OGRERR_NONE)
    7179             :         {
    7180             :             pszSQL =
    7181           1 :                 sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
    7182             :                                 "WHERE lower(tpudt_name) = lower('%q')",
    7183             :                                 pszLayerName);
    7184           1 :             eErr = SQLCommand(hDB, pszSQL);
    7185           1 :             sqlite3_free(pszSQL);
    7186             :         }
    7187             :     }
    7188             : 
    7189           2 :     if (eErr == OGRERR_NONE)
    7190             :     {
    7191           2 :         eErr = DeleteLayerCommon(pszLayerName);
    7192             :     }
    7193             : 
    7194           2 :     if (eErr == OGRERR_NONE)
    7195             :     {
    7196           2 :         eErr = SoftCommitTransaction();
    7197             :     }
    7198             :     else
    7199             :     {
    7200           0 :         SoftRollbackTransaction();
    7201             :     }
    7202             : 
    7203           4 :     return eErr;
    7204             : }
    7205             : 
    7206             : /************************************************************************/
    7207             : /*                    DeleteVectorOrRasterLayer()                       */
    7208             : /************************************************************************/
    7209             : 
    7210          13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
    7211             : {
    7212             : 
    7213          13 :     int idx = FindLayerIndex(pszLayerName);
    7214          13 :     if (idx >= 0)
    7215             :     {
    7216           5 :         DeleteLayer(idx);
    7217           5 :         return true;
    7218             :     }
    7219             : 
    7220             :     char *pszSQL =
    7221           8 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7222             :                         "lower(table_name) = lower('%q') "
    7223             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7224             :                         pszLayerName);
    7225           8 :     bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7226           8 :     sqlite3_free(pszSQL);
    7227           8 :     if (bIsRasterTable)
    7228             :     {
    7229           2 :         DeleteRasterLayer(pszLayerName);
    7230           2 :         return true;
    7231             :     }
    7232           6 :     return false;
    7233             : }
    7234             : 
    7235           7 : bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
    7236             :     const char *pszLayerName, const char *pszNewLayerName)
    7237             : {
    7238           7 :     int idx = FindLayerIndex(pszLayerName);
    7239           7 :     if (idx >= 0)
    7240             :     {
    7241           4 :         m_papoLayers[idx]->Rename(pszNewLayerName);
    7242           4 :         return true;
    7243             :     }
    7244             : 
    7245             :     char *pszSQL =
    7246           3 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7247             :                         "lower(table_name) = lower('%q') "
    7248             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7249             :                         pszLayerName);
    7250           3 :     const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7251           3 :     sqlite3_free(pszSQL);
    7252             : 
    7253           3 :     if (bIsRasterTable)
    7254             :     {
    7255           2 :         return RenameRasterLayer(pszLayerName, pszNewLayerName);
    7256             :     }
    7257             : 
    7258           1 :     return false;
    7259             : }
    7260             : 
    7261           2 : bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
    7262             :                                               const char *pszNewLayerName)
    7263             : {
    7264           4 :     std::string osSQL;
    7265             : 
    7266           2 :     char *pszSQL = sqlite3_mprintf(
    7267             :         "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
    7268             :         "AND type IN ('table', 'view')",
    7269             :         pszNewLayerName);
    7270           2 :     const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
    7271           2 :     sqlite3_free(pszSQL);
    7272           2 :     if (bAlreadyExists)
    7273             :     {
    7274           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
    7275             :                  pszNewLayerName);
    7276           0 :         return false;
    7277             :     }
    7278             : 
    7279             :     // Temporary remove foreign key checks
    7280             :     const GPKGTemporaryForeignKeyCheckDisabler
    7281           4 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7282             : 
    7283           2 :     if (SoftStartTransaction() != OGRERR_NONE)
    7284             :     {
    7285           0 :         return false;
    7286             :     }
    7287             : 
    7288           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
    7289             :                              "lower(table_name) = lower('%q');",
    7290             :                              pszNewLayerName, pszLayerName);
    7291           2 :     osSQL = pszSQL;
    7292           2 :     sqlite3_free(pszSQL);
    7293             : 
    7294           2 :     pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
    7295             :                              "lower(identifier) = lower('%q');",
    7296             :                              pszNewLayerName, pszLayerName);
    7297           2 :     osSQL += pszSQL;
    7298           2 :     sqlite3_free(pszSQL);
    7299             : 
    7300             :     pszSQL =
    7301           2 :         sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
    7302             :                         "lower(table_name) = lower('%q');",
    7303             :                         pszNewLayerName, pszLayerName);
    7304           2 :     osSQL += pszSQL;
    7305           2 :     sqlite3_free(pszSQL);
    7306             : 
    7307           2 :     pszSQL = sqlite3_mprintf(
    7308             :         "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
    7309             :         "lower(table_name) = lower('%q');",
    7310             :         pszNewLayerName, pszLayerName);
    7311           2 :     osSQL += pszSQL;
    7312           2 :     sqlite3_free(pszSQL);
    7313             : 
    7314           2 :     if (HasGriddedCoverageAncillaryTable())
    7315             :     {
    7316           1 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
    7317             :                                  "SET tile_matrix_set_name = '%q' WHERE "
    7318             :                                  "lower(tile_matrix_set_name) = lower('%q');",
    7319             :                                  pszNewLayerName, pszLayerName);
    7320           1 :         osSQL += pszSQL;
    7321           1 :         sqlite3_free(pszSQL);
    7322             : 
    7323           1 :         pszSQL = sqlite3_mprintf(
    7324             :             "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
    7325             :             "lower(tpudt_name) = lower('%q');",
    7326             :             pszNewLayerName, pszLayerName);
    7327           1 :         osSQL += pszSQL;
    7328           1 :         sqlite3_free(pszSQL);
    7329             :     }
    7330             : 
    7331           2 :     if (HasExtensionsTable())
    7332             :     {
    7333           2 :         pszSQL = sqlite3_mprintf(
    7334             :             "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
    7335             :             "lower(table_name) = lower('%q');",
    7336             :             pszNewLayerName, pszLayerName);
    7337           2 :         osSQL += pszSQL;
    7338           2 :         sqlite3_free(pszSQL);
    7339             :     }
    7340             : 
    7341           2 :     if (HasMetadataTables())
    7342             :     {
    7343           1 :         pszSQL = sqlite3_mprintf(
    7344             :             "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
    7345             :             "lower(table_name) = lower('%q');",
    7346             :             pszNewLayerName, pszLayerName);
    7347           1 :         osSQL += pszSQL;
    7348           1 :         sqlite3_free(pszSQL);
    7349             :     }
    7350             : 
    7351           2 :     if (HasDataColumnsTable())
    7352             :     {
    7353           0 :         pszSQL = sqlite3_mprintf(
    7354             :             "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
    7355             :             "lower(table_name) = lower('%q');",
    7356             :             pszNewLayerName, pszLayerName);
    7357           0 :         osSQL += pszSQL;
    7358           0 :         sqlite3_free(pszSQL);
    7359             :     }
    7360             : 
    7361           2 :     if (HasQGISLayerStyles())
    7362             :     {
    7363             :         // Update QGIS styles
    7364             :         pszSQL =
    7365           0 :             sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
    7366             :                             "lower(f_table_name) = lower('%q');",
    7367             :                             pszNewLayerName, pszLayerName);
    7368           0 :         osSQL += pszSQL;
    7369           0 :         sqlite3_free(pszSQL);
    7370             :     }
    7371             : 
    7372             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7373           2 :     if (m_bHasGPKGOGRContents)
    7374             :     {
    7375           2 :         pszSQL = sqlite3_mprintf(
    7376             :             "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
    7377             :             "lower(table_name) = lower('%q');",
    7378             :             pszNewLayerName, pszLayerName);
    7379           2 :         osSQL += pszSQL;
    7380           2 :         sqlite3_free(pszSQL);
    7381             :     }
    7382             : #endif
    7383             : 
    7384           2 :     if (HasGpkgextRelationsTable())
    7385             :     {
    7386           0 :         pszSQL = sqlite3_mprintf(
    7387             :             "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
    7388             :             "lower(base_table_name) = lower('%q');",
    7389             :             pszNewLayerName, pszLayerName);
    7390           0 :         osSQL += pszSQL;
    7391           0 :         sqlite3_free(pszSQL);
    7392             : 
    7393           0 :         pszSQL = sqlite3_mprintf(
    7394             :             "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
    7395             :             "lower(related_table_name) = lower('%q');",
    7396             :             pszNewLayerName, pszLayerName);
    7397           0 :         osSQL += pszSQL;
    7398           0 :         sqlite3_free(pszSQL);
    7399             : 
    7400           0 :         pszSQL = sqlite3_mprintf(
    7401             :             "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
    7402             :             "lower(mapping_table_name) = lower('%q');",
    7403             :             pszNewLayerName, pszLayerName);
    7404           0 :         osSQL += pszSQL;
    7405           0 :         sqlite3_free(pszSQL);
    7406             :     }
    7407             : 
    7408             :     // Drop all triggers for the layer
    7409           2 :     pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
    7410             :                              "'trigger' AND tbl_name = '%q'",
    7411             :                              pszLayerName);
    7412           2 :     auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
    7413           2 :     sqlite3_free(pszSQL);
    7414           2 :     if (oTriggerResult)
    7415             :     {
    7416          14 :         for (int i = 0; i < oTriggerResult->RowCount(); i++)
    7417             :         {
    7418          12 :             const char *pszTriggerName = oTriggerResult->GetValue(0, i);
    7419          12 :             pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
    7420             :                                      pszTriggerName);
    7421          12 :             osSQL += pszSQL;
    7422          12 :             sqlite3_free(pszSQL);
    7423             :         }
    7424             :     }
    7425             : 
    7426           2 :     pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
    7427             :                              pszLayerName, pszNewLayerName);
    7428           2 :     osSQL += pszSQL;
    7429           2 :     sqlite3_free(pszSQL);
    7430             : 
    7431             :     // Recreate all zoom/tile triggers
    7432           2 :     if (oTriggerResult)
    7433             :     {
    7434           2 :         osSQL += CreateRasterTriggersSQL(pszNewLayerName);
    7435             :     }
    7436             : 
    7437           2 :     OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
    7438             : 
    7439             :     // Check foreign key integrity
    7440           2 :     if (eErr == OGRERR_NONE)
    7441             :     {
    7442           2 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7443             :     }
    7444             : 
    7445           2 :     if (eErr == OGRERR_NONE)
    7446             :     {
    7447           2 :         eErr = SoftCommitTransaction();
    7448             :     }
    7449             :     else
    7450             :     {
    7451           0 :         SoftRollbackTransaction();
    7452             :     }
    7453             : 
    7454           2 :     return eErr == OGRERR_NONE;
    7455             : }
    7456             : 
    7457             : /************************************************************************/
    7458             : /*                       TestCapability()                               */
    7459             : /************************************************************************/
    7460             : 
    7461         320 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
    7462             : {
    7463         320 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7464         193 :         EQUAL(pszCap, "RenameLayer"))
    7465             :     {
    7466         127 :         return GetUpdate();
    7467             :     }
    7468         193 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7469          12 :         return TRUE;
    7470         181 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7471           8 :         return TRUE;
    7472         173 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7473           8 :         return TRUE;
    7474         165 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7475         165 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7476         165 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7477         165 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7478         165 :              EQUAL(pszCap, ODsCAddFieldDomain))
    7479           1 :         return GetUpdate();
    7480             : 
    7481         164 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7482             : }
    7483             : 
    7484             : /************************************************************************/
    7485             : /*                       ResetReadingAllLayers()                        */
    7486             : /************************************************************************/
    7487             : 
    7488          47 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7489             : {
    7490          99 :     for (int i = 0; i < m_nLayers; i++)
    7491             :     {
    7492          52 :         m_papoLayers[i]->ResetReading();
    7493             :     }
    7494          47 : }
    7495             : 
    7496             : /************************************************************************/
    7497             : /*                             ExecuteSQL()                             */
    7498             : /************************************************************************/
    7499             : 
    7500             : static const char *const apszFuncsWithSideEffects[] = {
    7501             :     "CreateSpatialIndex",
    7502             :     "DisableSpatialIndex",
    7503             :     "HasSpatialIndex",
    7504             :     "RegisterGeometryExtension",
    7505             : };
    7506             : 
    7507        5356 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7508             :                                             OGRGeometry *poSpatialFilter,
    7509             :                                             const char *pszDialect)
    7510             : 
    7511             : {
    7512        5356 :     m_bHasReadMetadataFromStorage = false;
    7513             : 
    7514        5356 :     FlushMetadata();
    7515             : 
    7516        5374 :     while (*pszSQLCommand != '\0' &&
    7517        5374 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7518          18 :         pszSQLCommand++;
    7519             : 
    7520       10712 :     CPLString osSQLCommand(pszSQLCommand);
    7521        5356 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7522          48 :         osSQLCommand.pop_back();
    7523             : 
    7524        5356 :     if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
    7525             :     {
    7526             :         // Some SQL commands will influence the feature count behind our
    7527             :         // back, so disable it in that case.
    7528             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7529             :         const bool bInsertOrDelete =
    7530        5287 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7531        2175 :             osSQLCommand.ifind("insert or replace into ") !=
    7532        7462 :                 std::string::npos ||
    7533        2129 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7534             :         const bool bRollback =
    7535        5287 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7536             : #endif
    7537             : 
    7538        6826 :         for (int i = 0; i < m_nLayers; i++)
    7539             :         {
    7540        1539 :             if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
    7541           0 :                 return nullptr;
    7542             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7543        1740 :             if (bRollback || (bInsertOrDelete &&
    7544         201 :                               osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
    7545             :                                   std::string::npos))
    7546             :             {
    7547         155 :                 m_papoLayers[i]->DisableFeatureCount();
    7548             :             }
    7549             : #endif
    7550             :         }
    7551             :     }
    7552             : 
    7553        5356 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7554        5355 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7555        5355 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7556        5355 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7557             :     {
    7558           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7559             :     }
    7560        5355 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7561        5354 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7562        5354 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7563        5354 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7564             :     {
    7565           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7566             :     }
    7567             : 
    7568             :     /* -------------------------------------------------------------------- */
    7569             :     /*      DEBUG "SELECT nolock" command.                                  */
    7570             :     /* -------------------------------------------------------------------- */
    7571        5425 :     if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
    7572          69 :         EQUAL(osSQLCommand, "SELECT nolock"))
    7573             :     {
    7574           3 :         return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
    7575             :     }
    7576             : 
    7577             :     /* -------------------------------------------------------------------- */
    7578             :     /*      Special case DELLAYER: command.                                 */
    7579             :     /* -------------------------------------------------------------------- */
    7580        5353 :     if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
    7581             :     {
    7582           4 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
    7583             : 
    7584           4 :         while (*pszLayerName == ' ')
    7585           0 :             pszLayerName++;
    7586             : 
    7587           4 :         if (!DeleteVectorOrRasterLayer(pszLayerName))
    7588             :         {
    7589           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7590             :                      pszLayerName);
    7591             :         }
    7592           4 :         return nullptr;
    7593             :     }
    7594             : 
    7595             :     /* -------------------------------------------------------------------- */
    7596             :     /*      Special case RECOMPUTE EXTENT ON command.                       */
    7597             :     /* -------------------------------------------------------------------- */
    7598        5349 :     if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
    7599             :     {
    7600             :         const char *pszLayerName =
    7601           4 :             osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
    7602             : 
    7603           4 :         while (*pszLayerName == ' ')
    7604           0 :             pszLayerName++;
    7605             : 
    7606           4 :         int idx = FindLayerIndex(pszLayerName);
    7607           4 :         if (idx >= 0)
    7608             :         {
    7609           4 :             m_papoLayers[idx]->RecomputeExtent();
    7610             :         }
    7611             :         else
    7612           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7613             :                      pszLayerName);
    7614           4 :         return nullptr;
    7615             :     }
    7616             : 
    7617             :     /* -------------------------------------------------------------------- */
    7618             :     /*      Intercept DROP TABLE                                            */
    7619             :     /* -------------------------------------------------------------------- */
    7620        5345 :     if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
    7621             :     {
    7622           9 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
    7623             : 
    7624           9 :         while (*pszLayerName == ' ')
    7625           0 :             pszLayerName++;
    7626             : 
    7627           9 :         if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
    7628           4 :             return nullptr;
    7629             :     }
    7630             : 
    7631             :     /* -------------------------------------------------------------------- */
    7632             :     /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
    7633             :     /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
    7634             :     /*      and       ALTER TABLE table DROP COLUMN col_name                */
    7635             :     /*                                                                      */
    7636             :     /*      We do this because SQLite mechanisms can't deal with updating   */
    7637             :     /*      literal values in gpkg_ tables that refer to table and column   */
    7638             :     /*      names.                                                          */
    7639             :     /* -------------------------------------------------------------------- */
    7640        5341 :     if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
    7641             :     {
    7642           9 :         char **papszTokens = SQLTokenize(osSQLCommand);
    7643             :         /* ALTER TABLE src_table RENAME TO dst_table */
    7644          16 :         if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
    7645           7 :             EQUAL(papszTokens[4], "TO"))
    7646             :         {
    7647           7 :             const char *pszSrcTableName = papszTokens[2];
    7648           7 :             const char *pszDstTableName = papszTokens[5];
    7649           7 :             if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
    7650          14 :                                           SQLUnescape(pszDstTableName)))
    7651             :             {
    7652           6 :                 CSLDestroy(papszTokens);
    7653           6 :                 return nullptr;
    7654             :             }
    7655             :         }
    7656             :         /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
    7657           2 :         else if (CSLCount(papszTokens) == 8 &&
    7658           1 :                  EQUAL(papszTokens[3], "RENAME") &&
    7659           3 :                  EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
    7660             :         {
    7661           1 :             const char *pszTableName = papszTokens[2];
    7662           1 :             const char *pszSrcColumn = papszTokens[5];
    7663           1 :             const char *pszDstColumn = papszTokens[7];
    7664             :             OGRGeoPackageTableLayer *poLayer =
    7665           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7666           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7667           1 :             if (poLayer)
    7668             :             {
    7669           2 :                 int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7670           2 :                     SQLUnescape(pszSrcColumn));
    7671           1 :                 if (nSrcFieldIdx >= 0)
    7672             :                 {
    7673             :                     // OFTString or any type will do as we just alter the name
    7674             :                     // so it will be ignored.
    7675           1 :                     OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
    7676           1 :                                             OFTString);
    7677           1 :                     poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
    7678             :                                             ALTER_NAME_FLAG);
    7679           1 :                     CSLDestroy(papszTokens);
    7680           1 :                     return nullptr;
    7681             :                 }
    7682             :             }
    7683             :         }
    7684             :         /* ALTER TABLE table DROP COLUMN col_name */
    7685           2 :         else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
    7686           1 :                  EQUAL(papszTokens[4], "COLUMN"))
    7687             :         {
    7688           1 :             const char *pszTableName = papszTokens[2];
    7689           1 :             const char *pszColumnName = papszTokens[5];
    7690             :             OGRGeoPackageTableLayer *poLayer =
    7691           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7692           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7693           1 :             if (poLayer)
    7694             :             {
    7695           2 :                 int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7696           2 :                     SQLUnescape(pszColumnName));
    7697           1 :                 if (nFieldIdx >= 0)
    7698             :                 {
    7699           1 :                     poLayer->DeleteField(nFieldIdx);
    7700           1 :                     CSLDestroy(papszTokens);
    7701           1 :                     return nullptr;
    7702             :                 }
    7703             :             }
    7704             :         }
    7705           1 :         CSLDestroy(papszTokens);
    7706             :     }
    7707             : 
    7708        5333 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7709             :     {
    7710          12 :         ResetReadingAllLayers();
    7711             :     }
    7712             : 
    7713        5333 :     if (EQUAL(osSQLCommand, "BEGIN"))
    7714             :     {
    7715           0 :         SoftStartTransaction();
    7716           0 :         return nullptr;
    7717             :     }
    7718        5333 :     else if (EQUAL(osSQLCommand, "COMMIT"))
    7719             :     {
    7720           0 :         SoftCommitTransaction();
    7721           0 :         return nullptr;
    7722             :     }
    7723        5333 :     else if (EQUAL(osSQLCommand, "ROLLBACK"))
    7724             :     {
    7725           0 :         SoftRollbackTransaction();
    7726           0 :         return nullptr;
    7727             :     }
    7728             : 
    7729        5333 :     else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
    7730             :     {
    7731             :         // Optimize truncation of a table, especially if it has a spatial
    7732             :         // index.
    7733          20 :         const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
    7734          20 :         if (aosTokens.size() == 3)
    7735             :         {
    7736          14 :             const char *pszTableName = aosTokens[2];
    7737             :             OGRGeoPackageTableLayer *poLayer =
    7738           8 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7739          22 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7740          14 :             if (poLayer)
    7741             :             {
    7742           6 :                 poLayer->Truncate();
    7743           6 :                 return nullptr;
    7744             :             }
    7745             :         }
    7746             :     }
    7747             : 
    7748        5313 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7749           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7750        5312 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7751          66 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7752          66 :              !EQUAL(pszDialect, "DEBUG"))
    7753           0 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7754           0 :                                        pszDialect);
    7755             : 
    7756             :     /* -------------------------------------------------------------------- */
    7757             :     /*      Prepare statement.                                              */
    7758             :     /* -------------------------------------------------------------------- */
    7759        5326 :     sqlite3_stmt *hSQLStmt = nullptr;
    7760             : 
    7761             :     /* This will speed-up layer creation */
    7762             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7763             :     /* the layer definition. */
    7764        5326 :     bool bUseStatementForGetNextFeature = true;
    7765        5326 :     bool bEmptyLayer = false;
    7766       10652 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7767             : 
    7768       17554 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7769        6114 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7770         754 :             std::string::npos &&
    7771         754 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7772        6868 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7773         754 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7774             :     {
    7775         754 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7776         754 :         if (nOrderByPos != std::string::npos)
    7777             :         {
    7778           8 :             osSQLCommandTruncated.resize(nOrderByPos);
    7779           8 :             bUseStatementForGetNextFeature = false;
    7780             :         }
    7781             :     }
    7782             : 
    7783        5326 :     int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7784        5326 :                         static_cast<int>(osSQLCommandTruncated.size()),
    7785             :                         &hSQLStmt, nullptr);
    7786             : 
    7787        5326 :     if (rc != SQLITE_OK)
    7788             :     {
    7789           9 :         CPLError(CE_Failure, CPLE_AppDefined,
    7790             :                  "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n  %s",
    7791             :                  osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7792             : 
    7793           9 :         if (hSQLStmt != nullptr)
    7794             :         {
    7795           0 :             sqlite3_finalize(hSQLStmt);
    7796             :         }
    7797             : 
    7798           9 :         return nullptr;
    7799             :     }
    7800             : 
    7801             :     /* -------------------------------------------------------------------- */
    7802             :     /*      Do we get a resultset?                                          */
    7803             :     /* -------------------------------------------------------------------- */
    7804        5317 :     rc = sqlite3_step(hSQLStmt);
    7805             : 
    7806        6878 :     for (int i = 0; i < m_nLayers; i++)
    7807             :     {
    7808        1561 :         m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
    7809             :     }
    7810             : 
    7811        5317 :     if (rc != SQLITE_ROW)
    7812             :     {
    7813        4606 :         if (rc != SQLITE_DONE)
    7814             :         {
    7815           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7816             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7817             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7818             : 
    7819           7 :             sqlite3_finalize(hSQLStmt);
    7820           7 :             return nullptr;
    7821             :         }
    7822             : 
    7823        4599 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7824             :         {
    7825          12 :             sqlite3_finalize(hSQLStmt);
    7826             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7827          12 :             SetApplicationAndUserVersionId();
    7828          12 :             return nullptr;
    7829             :         }
    7830             : 
    7831        4587 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7832             :         {
    7833        4466 :             sqlite3_finalize(hSQLStmt);
    7834        4466 :             return nullptr;
    7835             :         }
    7836             : 
    7837         121 :         bUseStatementForGetNextFeature = false;
    7838         121 :         bEmptyLayer = true;
    7839             :     }
    7840             : 
    7841             :     /* -------------------------------------------------------------------- */
    7842             :     /*      Special case for some functions which must be run               */
    7843             :     /*      only once                                                       */
    7844             :     /* -------------------------------------------------------------------- */
    7845         832 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7846             :     {
    7847        3784 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7848             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7849             :              i++)
    7850             :         {
    7851        3053 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7852             :                        strlen(apszFuncsWithSideEffects[i])))
    7853             :             {
    7854         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7855          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7856             :                 {
    7857          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7858             : 
    7859          56 :                     sqlite3_finalize(hSQLStmt);
    7860             : 
    7861             :                     return new OGRSQLiteSingleFeatureLayer(
    7862          56 :                         apszFuncsWithSideEffects[i], ret);
    7863             :                 }
    7864             :             }
    7865             :         }
    7866             :     }
    7867          45 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7868             :     {
    7869          63 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7870          18 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7871             :         {
    7872          15 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7873             : 
    7874          15 :             sqlite3_finalize(hSQLStmt);
    7875             : 
    7876          15 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7877          15 :                                                    ret);
    7878             :         }
    7879          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7880           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7881             :         {
    7882             :             const char *pszRet = reinterpret_cast<const char *>(
    7883           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7884             : 
    7885             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7886           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7887             : 
    7888           3 :             sqlite3_finalize(hSQLStmt);
    7889             : 
    7890           3 :             return poRet;
    7891             :         }
    7892             :     }
    7893             : 
    7894             :     /* -------------------------------------------------------------------- */
    7895             :     /*      Create layer.                                                   */
    7896             :     /* -------------------------------------------------------------------- */
    7897             : 
    7898             :     OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
    7899             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7900         758 :         bEmptyLayer);
    7901             : 
    7902         761 :     if (poSpatialFilter != nullptr &&
    7903           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7904           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7905             : 
    7906         758 :     return poLayer;
    7907             : }
    7908             : 
    7909             : /************************************************************************/
    7910             : /*                          ReleaseResultSet()                          */
    7911             : /************************************************************************/
    7912             : 
    7913         788 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7914             : 
    7915             : {
    7916         788 :     delete poLayer;
    7917         788 : }
    7918             : 
    7919             : /************************************************************************/
    7920             : /*                         HasExtensionsTable()                         */
    7921             : /************************************************************************/
    7922             : 
    7923        5616 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7924             : {
    7925        5616 :     return SQLGetInteger(
    7926             :                hDB,
    7927             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7928             :                "AND type IN ('table', 'view')",
    7929        5616 :                nullptr) == 1;
    7930             : }
    7931             : 
    7932             : /************************************************************************/
    7933             : /*                    CheckUnknownExtensions()                          */
    7934             : /************************************************************************/
    7935             : 
    7936        1327 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7937             : {
    7938        1327 :     if (!HasExtensionsTable())
    7939         198 :         return;
    7940             : 
    7941        1129 :     char *pszSQL = nullptr;
    7942        1129 :     if (!bCheckRasterTable)
    7943         931 :         pszSQL = sqlite3_mprintf(
    7944             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7945             :             "WHERE (table_name IS NULL "
    7946             :             "AND extension_name IS NOT NULL "
    7947             :             "AND definition IS NOT NULL "
    7948             :             "AND scope IS NOT NULL "
    7949             :             "AND extension_name NOT IN ("
    7950             :             "'gdal_aspatial', "
    7951             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7952             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7953             :                                        // 17-066r1 finalization
    7954             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7955             :             "'gpkg_metadata', "
    7956             :             "'gpkg_schema', "
    7957             :             "'gpkg_crs_wkt', "
    7958             :             "'gpkg_crs_wkt_1_1', "
    7959             :             "'related_tables', 'gpkg_related_tables')) "
    7960             : #ifdef WORKAROUND_SQLITE3_BUGS
    7961             :             "OR 0 "
    7962             : #endif
    7963             :             "LIMIT 1000");
    7964             :     else
    7965         198 :         pszSQL = sqlite3_mprintf(
    7966             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7967             :             "WHERE (lower(table_name) = lower('%q') "
    7968             :             "AND extension_name IS NOT NULL "
    7969             :             "AND definition IS NOT NULL "
    7970             :             "AND scope IS NOT NULL "
    7971             :             "AND extension_name NOT IN ("
    7972             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7973             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7974             :                                        // 17-066r1 finalization
    7975             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7976             :             "'gpkg_metadata', "
    7977             :             "'gpkg_schema', "
    7978             :             "'gpkg_crs_wkt', "
    7979             :             "'gpkg_crs_wkt_1_1', "
    7980             :             "'related_tables', 'gpkg_related_tables')) "
    7981             : #ifdef WORKAROUND_SQLITE3_BUGS
    7982             :             "OR 0 "
    7983             : #endif
    7984             :             "LIMIT 1000",
    7985             :             m_osRasterTable.c_str());
    7986             : 
    7987        2258 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7988        1129 :     sqlite3_free(pszSQL);
    7989        1129 :     if (oResultTable && oResultTable->RowCount() > 0)
    7990             :     {
    7991          44 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7992             :         {
    7993          22 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7994          22 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7995          22 :             const char *pszScope = oResultTable->GetValue(2, i);
    7996          22 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7997             :                 pszScope == nullptr)
    7998             :             {
    7999           0 :                 continue;
    8000             :             }
    8001             : 
    8002          22 :             if (EQUAL(pszExtName, "gpkg_webp"))
    8003             :             {
    8004          16 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    8005             :                 {
    8006           1 :                     CPLError(
    8007             :                         CE_Warning, CPLE_AppDefined,
    8008             :                         "Table %s contains WEBP tiles, but GDAL configured "
    8009             :                         "without WEBP support. Data will be missing",
    8010             :                         m_osRasterTable.c_str());
    8011             :                 }
    8012          16 :                 m_eTF = GPKG_TF_WEBP;
    8013          16 :                 continue;
    8014             :             }
    8015           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    8016             :             {
    8017           2 :                 m_bZoomOther = true;
    8018           2 :                 continue;
    8019             :             }
    8020             : 
    8021           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    8022             :             {
    8023           1 :                 CPLError(
    8024             :                     CE_Warning, CPLE_AppDefined,
    8025             :                     "Database relies on the '%s' (%s) extension that should "
    8026             :                     "be implemented for safe write-support, but is not "
    8027             :                     "currently. "
    8028             :                     "Update of that database are strongly discouraged to avoid "
    8029             :                     "corruption.",
    8030             :                     pszExtName, pszDefinition);
    8031             :             }
    8032           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    8033             :             {
    8034           1 :                 CPLError(
    8035             :                     CE_Warning, CPLE_AppDefined,
    8036             :                     "Database relies on the '%s' (%s) extension that should "
    8037             :                     "be implemented in order to read/write it safely, but is "
    8038             :                     "not currently. "
    8039             :                     "Some data may be missing while reading that database, and "
    8040             :                     "updates are strongly discouraged.",
    8041             :                     pszExtName, pszDefinition);
    8042             :             }
    8043           2 :             else if (EQUAL(pszScope, "read-write") &&
    8044             :                      // None of the NGA extensions at
    8045             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    8046             :                      // affect read-only scenarios
    8047           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    8048             :             {
    8049           1 :                 CPLError(
    8050             :                     CE_Warning, CPLE_AppDefined,
    8051             :                     "Database relies on the '%s' (%s) extension that should "
    8052             :                     "be implemented in order to read it safely, but is not "
    8053             :                     "currently. "
    8054             :                     "Some data may be missing while reading that database.",
    8055             :                     pszExtName, pszDefinition);
    8056             :             }
    8057             :         }
    8058             :     }
    8059             : }
    8060             : 
    8061             : /************************************************************************/
    8062             : /*                         HasGDALAspatialExtension()                       */
    8063             : /************************************************************************/
    8064             : 
    8065         885 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    8066             : {
    8067         885 :     if (!HasExtensionsTable())
    8068          91 :         return false;
    8069             : 
    8070             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    8071             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    8072             :                                       "AND table_name IS NULL "
    8073             :                                       "AND column_name IS NULL)"
    8074             : #ifdef WORKAROUND_SQLITE3_BUGS
    8075             :                                       " OR 0"
    8076             : #endif
    8077         794 :     );
    8078         794 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    8079         794 :     return bHasExtension;
    8080             : }
    8081             : 
    8082             : std::string
    8083         168 : GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
    8084             : {
    8085             :     char *pszSQL;
    8086         168 :     std::string osSQL;
    8087             :     /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    8088             :      * Definition SQL  */
    8089         168 :     pszSQL = sqlite3_mprintf(
    8090             :         "CREATE TRIGGER \"%w_zoom_insert\" "
    8091             :         "BEFORE INSERT ON \"%w\" "
    8092             :         "FOR EACH ROW BEGIN "
    8093             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8094             :         "constraint: zoom_level not specified for table in "
    8095             :         "gpkg_tile_matrix') "
    8096             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    8097             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    8098             :         "END; "
    8099             :         "CREATE TRIGGER \"%w_zoom_update\" "
    8100             :         "BEFORE UPDATE OF zoom_level ON \"%w\" "
    8101             :         "FOR EACH ROW BEGIN "
    8102             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8103             :         "constraint: zoom_level not specified for table in "
    8104             :         "gpkg_tile_matrix') "
    8105             :         "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    8106             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    8107             :         "END; "
    8108             :         "CREATE TRIGGER \"%w_tile_column_insert\" "
    8109             :         "BEFORE INSERT ON \"%w\" "
    8110             :         "FOR EACH ROW BEGIN "
    8111             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8112             :         "constraint: tile_column cannot be < 0') "
    8113             :         "WHERE (NEW.tile_column < 0) ; "
    8114             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8115             :         "constraint: tile_column must by < matrix_width specified for "
    8116             :         "table and zoom level in gpkg_tile_matrix') "
    8117             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8118             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8119             :         "zoom_level = NEW.zoom_level)); "
    8120             :         "END; "
    8121             :         "CREATE TRIGGER \"%w_tile_column_update\" "
    8122             :         "BEFORE UPDATE OF tile_column ON \"%w\" "
    8123             :         "FOR EACH ROW BEGIN "
    8124             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8125             :         "constraint: tile_column cannot be < 0') "
    8126             :         "WHERE (NEW.tile_column < 0) ; "
    8127             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8128             :         "constraint: tile_column must by < matrix_width specified for "
    8129             :         "table and zoom level in gpkg_tile_matrix') "
    8130             :         "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    8131             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8132             :         "zoom_level = NEW.zoom_level)); "
    8133             :         "END; "
    8134             :         "CREATE TRIGGER \"%w_tile_row_insert\" "
    8135             :         "BEFORE INSERT ON \"%w\" "
    8136             :         "FOR EACH ROW BEGIN "
    8137             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8138             :         "constraint: tile_row cannot be < 0') "
    8139             :         "WHERE (NEW.tile_row < 0) ; "
    8140             :         "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    8141             :         "constraint: tile_row must by < matrix_height specified for "
    8142             :         "table and zoom level in gpkg_tile_matrix') "
    8143             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8144             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8145             :         "zoom_level = NEW.zoom_level)); "
    8146             :         "END; "
    8147             :         "CREATE TRIGGER \"%w_tile_row_update\" "
    8148             :         "BEFORE UPDATE OF tile_row ON \"%w\" "
    8149             :         "FOR EACH ROW BEGIN "
    8150             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8151             :         "constraint: tile_row cannot be < 0') "
    8152             :         "WHERE (NEW.tile_row < 0) ; "
    8153             :         "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    8154             :         "constraint: tile_row must by < matrix_height specified for "
    8155             :         "table and zoom level in gpkg_tile_matrix') "
    8156             :         "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    8157             :         "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    8158             :         "zoom_level = NEW.zoom_level)); "
    8159             :         "END; ",
    8160             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8161             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8162             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8163             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8164             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8165             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8166             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8167             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8168             :         osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
    8169             :         osTableName.c_str());
    8170         168 :     osSQL = pszSQL;
    8171         168 :     sqlite3_free(pszSQL);
    8172         168 :     return osSQL;
    8173             : }
    8174             : 
    8175             : /************************************************************************/
    8176             : /*                  CreateExtensionsTableIfNecessary()                  */
    8177             : /************************************************************************/
    8178             : 
    8179        1017 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    8180             : {
    8181             :     /* Check if the table gpkg_extensions exists */
    8182        1017 :     if (HasExtensionsTable())
    8183         372 :         return OGRERR_NONE;
    8184             : 
    8185             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    8186             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    8187             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    8188             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    8189         645 :     const char *pszCreateGpkgExtensions =
    8190             :         "CREATE TABLE gpkg_extensions ("
    8191             :         "table_name TEXT,"
    8192             :         "column_name TEXT,"
    8193             :         "extension_name TEXT NOT NULL,"
    8194             :         "definition TEXT NOT NULL,"
    8195             :         "scope TEXT NOT NULL,"
    8196             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    8197             :         ")";
    8198             : 
    8199         645 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    8200             : }
    8201             : 
    8202             : /************************************************************************/
    8203             : /*                    OGR_GPKG_Intersects_Spatial_Filter()              */
    8204             : /************************************************************************/
    8205             : 
    8206       23112 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    8207             :                                         sqlite3_value **argv)
    8208             : {
    8209       23112 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8210             :     {
    8211           0 :         sqlite3_result_int(pContext, 0);
    8212       23102 :         return;
    8213             :     }
    8214             : 
    8215             :     auto poLayer =
    8216       23112 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    8217             : 
    8218       23112 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8219             :     const GByte *pabyBLOB =
    8220       23112 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8221             : 
    8222             :     GPkgHeader sHeader;
    8223       46224 :     if (poLayer->m_bFilterIsEnvelope &&
    8224       23112 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    8225             :     {
    8226       23112 :         if (sHeader.bExtentHasXY)
    8227             :         {
    8228          72 :             OGREnvelope sEnvelope;
    8229          72 :             sEnvelope.MinX = sHeader.MinX;
    8230          72 :             sEnvelope.MinY = sHeader.MinY;
    8231          72 :             sEnvelope.MaxX = sHeader.MaxX;
    8232          72 :             sEnvelope.MaxY = sHeader.MaxY;
    8233          72 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    8234             :             {
    8235          17 :                 sqlite3_result_int(pContext, 1);
    8236          17 :                 return;
    8237             :             }
    8238             :         }
    8239             : 
    8240             :         // Check if at least one point falls into the layer filter envelope
    8241             :         // nHeaderLen is > 0 for GeoPackage geometries
    8242       46190 :         if (sHeader.nHeaderLen > 0 &&
    8243       23095 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8244       23095 :                                         nBLOBLen - sHeader.nHeaderLen,
    8245       23095 :                                         poLayer->m_sFilterEnvelope))
    8246             :         {
    8247       23085 :             sqlite3_result_int(pContext, 1);
    8248       23085 :             return;
    8249             :         }
    8250             :     }
    8251             : 
    8252             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8253          10 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8254          10 :     if (poGeom == nullptr)
    8255             :     {
    8256             :         // Try also spatialite geometry blobs
    8257           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8258           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8259           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8260             :         {
    8261           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8262           0 :             sqlite3_result_int(pContext, 0);
    8263           0 :             return;
    8264             :         }
    8265           0 :         poGeom.reset(poGeomSpatialite);
    8266             :     }
    8267             : 
    8268          10 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8269             : }
    8270             : 
    8271             : /************************************************************************/
    8272             : /*                      OGRGeoPackageSTMinX()                           */
    8273             : /************************************************************************/
    8274             : 
    8275      242445 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8276             :                                 sqlite3_value **argv)
    8277             : {
    8278             :     GPkgHeader sHeader;
    8279      242445 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8280             :     {
    8281           3 :         sqlite3_result_null(pContext);
    8282           3 :         return;
    8283             :     }
    8284      242442 :     sqlite3_result_double(pContext, sHeader.MinX);
    8285             : }
    8286             : 
    8287             : /************************************************************************/
    8288             : /*                      OGRGeoPackageSTMinY()                           */
    8289             : /************************************************************************/
    8290             : 
    8291      242443 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8292             :                                 sqlite3_value **argv)
    8293             : {
    8294             :     GPkgHeader sHeader;
    8295      242443 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8296             :     {
    8297           1 :         sqlite3_result_null(pContext);
    8298           1 :         return;
    8299             :     }
    8300      242442 :     sqlite3_result_double(pContext, sHeader.MinY);
    8301             : }
    8302             : 
    8303             : /************************************************************************/
    8304             : /*                      OGRGeoPackageSTMaxX()                           */
    8305             : /************************************************************************/
    8306             : 
    8307      242443 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8308             :                                 sqlite3_value **argv)
    8309             : {
    8310             :     GPkgHeader sHeader;
    8311      242443 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8312             :     {
    8313           1 :         sqlite3_result_null(pContext);
    8314           1 :         return;
    8315             :     }
    8316      242442 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8317             : }
    8318             : 
    8319             : /************************************************************************/
    8320             : /*                      OGRGeoPackageSTMaxY()                           */
    8321             : /************************************************************************/
    8322             : 
    8323      242443 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8324             :                                 sqlite3_value **argv)
    8325             : {
    8326             :     GPkgHeader sHeader;
    8327      242443 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8328             :     {
    8329           1 :         sqlite3_result_null(pContext);
    8330           1 :         return;
    8331             :     }
    8332      242442 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8333             : }
    8334             : 
    8335             : /************************************************************************/
    8336             : /*                     OGRGeoPackageSTIsEmpty()                         */
    8337             : /************************************************************************/
    8338             : 
    8339      243679 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8340             :                                    sqlite3_value **argv)
    8341             : {
    8342             :     GPkgHeader sHeader;
    8343      243679 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8344             :     {
    8345           2 :         sqlite3_result_null(pContext);
    8346           2 :         return;
    8347             :     }
    8348      243677 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8349             : }
    8350             : 
    8351             : /************************************************************************/
    8352             : /*                    OGRGeoPackageSTGeometryType()                     */
    8353             : /************************************************************************/
    8354             : 
    8355           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8356             :                                         sqlite3_value **argv)
    8357             : {
    8358             :     GPkgHeader sHeader;
    8359             : 
    8360           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8361             :     const GByte *pabyBLOB =
    8362           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8363             :     OGRwkbGeometryType eGeometryType;
    8364             : 
    8365          13 :     if (nBLOBLen < 8 ||
    8366           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8367             :     {
    8368           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8369             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8370           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8371             :         {
    8372           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8373             :                                 SQLITE_TRANSIENT);
    8374           4 :             return;
    8375             :         }
    8376             :         else
    8377             :         {
    8378           1 :             sqlite3_result_null(pContext);
    8379           1 :             return;
    8380             :         }
    8381             :     }
    8382             : 
    8383           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8384             :     {
    8385           2 :         sqlite3_result_null(pContext);
    8386           2 :         return;
    8387             :     }
    8388             : 
    8389           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8390             :                                         wkbVariantIso, &eGeometryType);
    8391           3 :     if (err != OGRERR_NONE)
    8392           1 :         sqlite3_result_null(pContext);
    8393             :     else
    8394           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8395             :                             SQLITE_TRANSIENT);
    8396             : }
    8397             : 
    8398             : /************************************************************************/
    8399             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8400             : /************************************************************************/
    8401             : 
    8402         358 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8403             :                                                int argc, sqlite3_value **argv)
    8404             : {
    8405             :     GPkgHeader sHeader;
    8406         358 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8407             :     {
    8408           2 :         sqlite3_result_int(pContext, FALSE);
    8409         197 :         return;
    8410             :     }
    8411         356 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8412         356 :     if (sHeader.MaxX < dfMinX)
    8413             :     {
    8414         105 :         sqlite3_result_int(pContext, FALSE);
    8415         105 :         return;
    8416             :     }
    8417         251 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8418         251 :     if (sHeader.MaxY < dfMinY)
    8419             :     {
    8420          23 :         sqlite3_result_int(pContext, FALSE);
    8421          23 :         return;
    8422             :     }
    8423         228 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8424         228 :     if (sHeader.MinX > dfMaxX)
    8425             :     {
    8426          67 :         sqlite3_result_int(pContext, FALSE);
    8427          67 :         return;
    8428             :     }
    8429         161 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8430         161 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8431             : }
    8432             : 
    8433             : /************************************************************************/
    8434             : /*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
    8435             : /************************************************************************/
    8436             : 
    8437             : static void
    8438           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8439             :                                             sqlite3_value **argv)
    8440             : {
    8441             :     GPkgHeader sHeader;
    8442           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8443             :     {
    8444           0 :         sqlite3_result_int(pContext, FALSE);
    8445           2 :         return;
    8446             :     }
    8447             :     GPkgHeader sHeader2;
    8448           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8449             :                                 1))
    8450             :     {
    8451           0 :         sqlite3_result_int(pContext, FALSE);
    8452           0 :         return;
    8453             :     }
    8454           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8455             :     {
    8456           1 :         sqlite3_result_int(pContext, FALSE);
    8457           1 :         return;
    8458             :     }
    8459           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8460             :     {
    8461           0 :         sqlite3_result_int(pContext, FALSE);
    8462           0 :         return;
    8463             :     }
    8464           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8465             :     {
    8466           1 :         sqlite3_result_int(pContext, FALSE);
    8467           1 :         return;
    8468             :     }
    8469           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8470             : }
    8471             : 
    8472             : /************************************************************************/
    8473             : /*                    OGRGeoPackageGPKGIsAssignable()                   */
    8474             : /************************************************************************/
    8475             : 
    8476           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8477             :                                           int /*argc*/, sqlite3_value **argv)
    8478             : {
    8479          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8480           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8481             :     {
    8482           2 :         sqlite3_result_int(pContext, 0);
    8483           2 :         return;
    8484             :     }
    8485             : 
    8486             :     const char *pszExpected =
    8487           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8488             :     const char *pszActual =
    8489           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8490           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8491             :                                             OGRFromOGCGeomType(pszExpected));
    8492           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8493             : }
    8494             : 
    8495             : /************************************************************************/
    8496             : /*                     OGRGeoPackageSTSRID()                            */
    8497             : /************************************************************************/
    8498             : 
    8499          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8500             :                                 sqlite3_value **argv)
    8501             : {
    8502             :     GPkgHeader sHeader;
    8503          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8504             :     {
    8505           2 :         sqlite3_result_null(pContext);
    8506           2 :         return;
    8507             :     }
    8508          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8509             : }
    8510             : 
    8511             : /************************************************************************/
    8512             : /*                     OGRGeoPackageSetSRID()                           */
    8513             : /************************************************************************/
    8514             : 
    8515          28 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8516             :                                  sqlite3_value **argv)
    8517             : {
    8518          28 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8519             :     {
    8520           1 :         sqlite3_result_null(pContext);
    8521           1 :         return;
    8522             :     }
    8523          27 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8524             :     GPkgHeader sHeader;
    8525          27 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8526             :     const GByte *pabyBLOB =
    8527          27 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8528             : 
    8529          54 :     if (nBLOBLen < 8 ||
    8530          27 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8531             :     {
    8532             :         // Try also spatialite geometry blobs
    8533           0 :         OGRGeometry *poGeom = nullptr;
    8534           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8535             :             OGRERR_NONE)
    8536             :         {
    8537           0 :             sqlite3_result_null(pContext);
    8538           0 :             return;
    8539             :         }
    8540           0 :         size_t nBLOBDestLen = 0;
    8541             :         GByte *pabyDestBLOB =
    8542           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8543           0 :         if (!pabyDestBLOB)
    8544             :         {
    8545           0 :             sqlite3_result_null(pContext);
    8546           0 :             return;
    8547             :         }
    8548           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8549             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8550           0 :         return;
    8551             :     }
    8552             : 
    8553          27 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8554          27 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8555          27 :     int32_t nSRIDToSerialize = nDestSRID;
    8556          27 :     if (OGR_SWAP(sHeader.eByteOrder))
    8557           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8558          27 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8559          27 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8560             : }
    8561             : 
    8562             : /************************************************************************/
    8563             : /*                   OGRGeoPackageSTMakeValid()                         */
    8564             : /************************************************************************/
    8565             : 
    8566           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8567             :                                      sqlite3_value **argv)
    8568             : {
    8569           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8570             :     {
    8571           2 :         sqlite3_result_null(pContext);
    8572           2 :         return;
    8573             :     }
    8574           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8575             :     const GByte *pabyBLOB =
    8576           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8577             : 
    8578             :     GPkgHeader sHeader;
    8579           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8580             :     {
    8581           0 :         sqlite3_result_null(pContext);
    8582           0 :         return;
    8583             :     }
    8584             : 
    8585             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8586           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8587           1 :     if (poGeom == nullptr)
    8588             :     {
    8589             :         // Try also spatialite geometry blobs
    8590           0 :         OGRGeometry *poGeomPtr = nullptr;
    8591           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8592             :             OGRERR_NONE)
    8593             :         {
    8594           0 :             sqlite3_result_null(pContext);
    8595           0 :             return;
    8596             :         }
    8597           0 :         poGeom.reset(poGeomPtr);
    8598             :     }
    8599           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8600           1 :     if (poValid == nullptr)
    8601             :     {
    8602           0 :         sqlite3_result_null(pContext);
    8603           0 :         return;
    8604             :     }
    8605             : 
    8606           1 :     size_t nBLOBDestLen = 0;
    8607           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8608             :                                               nullptr, &nBLOBDestLen);
    8609           1 :     if (!pabyDestBLOB)
    8610             :     {
    8611           0 :         sqlite3_result_null(pContext);
    8612           0 :         return;
    8613             :     }
    8614           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8615             :                         VSIFree);
    8616             : }
    8617             : 
    8618             : /************************************************************************/
    8619             : /*                   OGRGeoPackageSTArea()                              */
    8620             : /************************************************************************/
    8621             : 
    8622          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8623             :                                 sqlite3_value **argv)
    8624             : {
    8625          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8626             :     {
    8627           1 :         sqlite3_result_null(pContext);
    8628          15 :         return;
    8629             :     }
    8630          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8631             :     const GByte *pabyBLOB =
    8632          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8633             : 
    8634             :     GPkgHeader sHeader;
    8635           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8636          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8637             :     {
    8638          16 :         if (sHeader.bEmpty)
    8639             :         {
    8640           3 :             sqlite3_result_double(pContext, 0);
    8641          13 :             return;
    8642             :         }
    8643          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8644          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8645             :         bool bNeedSwap;
    8646             :         uint32_t nType;
    8647          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8648             :         {
    8649          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8650          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8651          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8652             :             {
    8653             :                 double dfArea;
    8654           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8655             :                 {
    8656           5 :                     sqlite3_result_double(pContext, dfArea);
    8657           5 :                     return;
    8658           0 :                 }
    8659             :             }
    8660           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8661           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8662           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8663             :             {
    8664             :                 double dfArea;
    8665           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8666             :                 {
    8667           5 :                     sqlite3_result_double(pContext, dfArea);
    8668           5 :                     return;
    8669             :                 }
    8670             :             }
    8671             :         }
    8672             : 
    8673             :         // For curve geometries, fallback to OGRGeometry methods
    8674           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8675             :     }
    8676             :     else
    8677             :     {
    8678             :         // Try also spatialite geometry blobs
    8679           2 :         OGRGeometry *poGeomPtr = nullptr;
    8680           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8681             :             OGRERR_NONE)
    8682             :         {
    8683           1 :             sqlite3_result_null(pContext);
    8684           1 :             return;
    8685             :         }
    8686           1 :         poGeom.reset(poGeomPtr);
    8687             :     }
    8688           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8689           4 :     if (poSurface == nullptr)
    8690             :     {
    8691           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8692           2 :         if (poMultiSurface == nullptr)
    8693             :         {
    8694           1 :             sqlite3_result_double(pContext, 0);
    8695             :         }
    8696             :         else
    8697             :         {
    8698           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8699             :         }
    8700             :     }
    8701             :     else
    8702             :     {
    8703           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8704             :     }
    8705             : }
    8706             : 
    8707             : /************************************************************************/
    8708             : /*                     OGRGeoPackageGeodesicArea()                      */
    8709             : /************************************************************************/
    8710             : 
    8711           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8712             :                                       sqlite3_value **argv)
    8713             : {
    8714           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8715             :     {
    8716           1 :         sqlite3_result_null(pContext);
    8717           3 :         return;
    8718             :     }
    8719           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8720             :     {
    8721           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8722             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8723             :                  "use_ellipsoid = 1");
    8724             :     }
    8725             : 
    8726           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8727             :     const GByte *pabyBLOB =
    8728           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8729             :     GPkgHeader sHeader;
    8730           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8731             :     {
    8732           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8733           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8734           1 :         return;
    8735             :     }
    8736             : 
    8737             :     GDALGeoPackageDataset *poDS =
    8738           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8739             : 
    8740             :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
    8741           3 :         poDS->GetSpatialRef(sHeader.iSrsId, true));
    8742           3 :     if (poSrcSRS == nullptr)
    8743             :     {
    8744           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8745             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8746           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8747           1 :         return;
    8748             :     }
    8749             : 
    8750             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8751           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8752           2 :     if (poGeom == nullptr)
    8753             :     {
    8754             :         // Try also spatialite geometry blobs
    8755           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8756           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8757           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8758             :         {
    8759           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8760           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8761           0 :             return;
    8762             :         }
    8763           0 :         poGeom.reset(poGeomSpatialite);
    8764             :     }
    8765             : 
    8766           2 :     poGeom->assignSpatialReference(poSrcSRS.get());
    8767           2 :     sqlite3_result_double(
    8768             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8769             : }
    8770             : 
    8771             : /************************************************************************/
    8772             : /*                   OGRGeoPackageLengthOrGeodesicLength()              */
    8773             : /************************************************************************/
    8774             : 
    8775           8 : static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
    8776             :                                                 int argc, sqlite3_value **argv)
    8777             : {
    8778           8 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8779             :     {
    8780           2 :         sqlite3_result_null(pContext);
    8781           5 :         return;
    8782             :     }
    8783           6 :     if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
    8784             :     {
    8785           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8786             :                  "ST_Length(geom, use_ellipsoid) is only supported for "
    8787             :                  "use_ellipsoid = 1");
    8788             :     }
    8789             : 
    8790           6 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8791             :     const GByte *pabyBLOB =
    8792           6 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8793             :     GPkgHeader sHeader;
    8794           6 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8795             :     {
    8796           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8797           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8798           2 :         return;
    8799             :     }
    8800             : 
    8801             :     GDALGeoPackageDataset *poDS =
    8802           4 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8803             : 
    8804           0 :     std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
    8805           4 :     if (argc == 2)
    8806             :     {
    8807           3 :         poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8808           3 :         if (!poSrcSRS)
    8809             :         {
    8810           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    8811             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8812           1 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8813           1 :             return;
    8814             :         }
    8815             :     }
    8816             : 
    8817             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8818           3 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8819           3 :     if (poGeom == nullptr)
    8820             :     {
    8821             :         // Try also spatialite geometry blobs
    8822           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8823           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8824           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8825             :         {
    8826           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8827           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8828           0 :             return;
    8829             :         }
    8830           0 :         poGeom.reset(poGeomSpatialite);
    8831             :     }
    8832             : 
    8833           3 :     if (argc == 2)
    8834           2 :         poGeom->assignSpatialReference(poSrcSRS.get());
    8835             : 
    8836           6 :     sqlite3_result_double(
    8837             :         pContext,
    8838           1 :         argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
    8839           2 :                   : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
    8840             : }
    8841             : 
    8842             : /************************************************************************/
    8843             : /*                      OGRGeoPackageTransform()                        */
    8844             : /************************************************************************/
    8845             : 
    8846             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8847             :                             sqlite3_value **argv);
    8848             : 
    8849          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8850             :                             sqlite3_value **argv)
    8851             : {
    8852          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8853          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8854             :     {
    8855           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8856          32 :         return;
    8857             :     }
    8858             : 
    8859          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8860             :     const GByte *pabyBLOB =
    8861          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8862             :     GPkgHeader sHeader;
    8863          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8864             :     {
    8865           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8866           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8867           1 :         return;
    8868             :     }
    8869             : 
    8870          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8871          29 :     if (sHeader.iSrsId == nDestSRID)
    8872             :     {
    8873             :         // Return blob unmodified
    8874           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8875           3 :         return;
    8876             :     }
    8877             : 
    8878             :     GDALGeoPackageDataset *poDS =
    8879          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8880             : 
    8881             :     // Try to get the cached coordinate transformation
    8882             :     OGRCoordinateTransformation *poCT;
    8883          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8884          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8885             :     {
    8886          20 :         poCT = poDS->m_poLastCachedCT.get();
    8887             :     }
    8888             :     else
    8889             :     {
    8890             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8891           6 :             poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
    8892           6 :         if (poSrcSRS == nullptr)
    8893             :         {
    8894           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8895             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8896           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8897           0 :             return;
    8898             :         }
    8899             : 
    8900             :         std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
    8901           6 :             poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
    8902           6 :         if (poDstSRS == nullptr)
    8903             :         {
    8904           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8905             :                      nDestSRID);
    8906           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8907           0 :             return;
    8908             :         }
    8909             :         poCT =
    8910           6 :             OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
    8911           6 :         if (poCT == nullptr)
    8912             :         {
    8913           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8914           0 :             return;
    8915             :         }
    8916             : 
    8917             :         // Cache coordinate transformation for potential later reuse
    8918           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8919           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8920           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8921           6 :         poCT = poDS->m_poLastCachedCT.get();
    8922             :     }
    8923             : 
    8924          26 :     if (sHeader.nHeaderLen >= 8)
    8925             :     {
    8926          26 :         std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
    8927          26 :         abyNewBLOB.resize(nBLOBLen);
    8928          26 :         memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
    8929             : 
    8930          26 :         OGREnvelope3D oEnv3d;
    8931          26 :         if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
    8932          26 :                              nBLOBLen - sHeader.nHeaderLen, poCT,
    8933          78 :                              poDS->m_oWKBTransformCache, oEnv3d) ||
    8934          26 :             !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
    8935             :                               oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
    8936             :                               oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
    8937             :         {
    8938           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8939           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8940           0 :             return;
    8941             :         }
    8942             : 
    8943          26 :         sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
    8944             :                             SQLITE_TRANSIENT);
    8945          26 :         return;
    8946             :     }
    8947             : 
    8948             :     // Try also spatialite geometry blobs
    8949           0 :     OGRGeometry *poGeomSpatialite = nullptr;
    8950           0 :     if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8951           0 :                                           &poGeomSpatialite) != OGRERR_NONE)
    8952             :     {
    8953           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8954           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8955           0 :         return;
    8956             :     }
    8957           0 :     auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
    8958             : 
    8959           0 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8960             :     {
    8961           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8962           0 :         return;
    8963             :     }
    8964             : 
    8965           0 :     size_t nBLOBDestLen = 0;
    8966             :     GByte *pabyDestBLOB =
    8967           0 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8968           0 :     if (!pabyDestBLOB)
    8969             :     {
    8970           0 :         sqlite3_result_null(pContext);
    8971           0 :         return;
    8972             :     }
    8973           0 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8974             :                         VSIFree);
    8975             : }
    8976             : 
    8977             : /************************************************************************/
    8978             : /*                      OGRGeoPackageSridFromAuthCRS()                  */
    8979             : /************************************************************************/
    8980             : 
    8981           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8982             :                                          int /*argc*/, sqlite3_value **argv)
    8983             : {
    8984           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8985           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8986             :     {
    8987           2 :         sqlite3_result_int(pContext, -1);
    8988           2 :         return;
    8989             :     }
    8990             : 
    8991             :     GDALGeoPackageDataset *poDS =
    8992           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8993             : 
    8994           2 :     char *pszSQL = sqlite3_mprintf(
    8995             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8996             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8997           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8998           2 :     OGRErr err = OGRERR_NONE;
    8999           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    9000           2 :     sqlite3_free(pszSQL);
    9001           2 :     if (err != OGRERR_NONE)
    9002           1 :         nSRSId = -1;
    9003           2 :     sqlite3_result_int(pContext, nSRSId);
    9004             : }
    9005             : 
    9006             : /************************************************************************/
    9007             : /*                    OGRGeoPackageImportFromEPSG()                     */
    9008             : /************************************************************************/
    9009             : 
    9010           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    9011             :                                         sqlite3_value **argv)
    9012             : {
    9013           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    9014             :     {
    9015           1 :         sqlite3_result_int(pContext, -1);
    9016           2 :         return;
    9017             :     }
    9018             : 
    9019             :     GDALGeoPackageDataset *poDS =
    9020           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9021           3 :     OGRSpatialReference oSRS;
    9022           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    9023             :     {
    9024           1 :         sqlite3_result_int(pContext, -1);
    9025           1 :         return;
    9026             :     }
    9027             : 
    9028           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    9029             : }
    9030             : 
    9031             : /************************************************************************/
    9032             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    9033             : /************************************************************************/
    9034             : 
    9035           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    9036             :                                                    int /*argc*/,
    9037             :                                                    sqlite3_value **argv)
    9038             : {
    9039           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9040           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    9041           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    9042             :     {
    9043           0 :         sqlite3_result_int(pContext, 0);
    9044           0 :         return;
    9045             :     }
    9046             : 
    9047             :     const char *pszTableName =
    9048           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9049             :     const char *pszGeomName =
    9050           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9051             :     const char *pszGeomType =
    9052           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    9053             : 
    9054             :     GDALGeoPackageDataset *poDS =
    9055           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9056             : 
    9057           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9058           1 :         poDS->GetLayerByName(pszTableName));
    9059           1 :     if (poLyr == nullptr)
    9060             :     {
    9061           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9062           0 :         sqlite3_result_int(pContext, 0);
    9063           0 :         return;
    9064             :     }
    9065           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9066             :     {
    9067           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9068           0 :         sqlite3_result_int(pContext, 0);
    9069           0 :         return;
    9070             :     }
    9071           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    9072           1 :     if (eGeomType == wkbUnknown)
    9073             :     {
    9074           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    9075           0 :         sqlite3_result_int(pContext, 0);
    9076           0 :         return;
    9077             :     }
    9078             : 
    9079           1 :     sqlite3_result_int(
    9080             :         pContext,
    9081           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    9082             : }
    9083             : 
    9084             : /************************************************************************/
    9085             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    9086             : /************************************************************************/
    9087             : 
    9088          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    9089             :                                             int /*argc*/, sqlite3_value **argv)
    9090             : {
    9091          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9092          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9093             :     {
    9094           2 :         sqlite3_result_int(pContext, 0);
    9095           2 :         return;
    9096             :     }
    9097             : 
    9098             :     const char *pszTableName =
    9099          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9100             :     const char *pszGeomName =
    9101          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9102             :     GDALGeoPackageDataset *poDS =
    9103          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9104             : 
    9105          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9106          12 :         poDS->GetLayerByName(pszTableName));
    9107          12 :     if (poLyr == nullptr)
    9108             :     {
    9109           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9110           1 :         sqlite3_result_int(pContext, 0);
    9111           1 :         return;
    9112             :     }
    9113          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9114             :     {
    9115           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9116           1 :         sqlite3_result_int(pContext, 0);
    9117           1 :         return;
    9118             :     }
    9119             : 
    9120          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    9121             : }
    9122             : 
    9123             : /************************************************************************/
    9124             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    9125             : /************************************************************************/
    9126             : 
    9127          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    9128             :                                              int /*argc*/, sqlite3_value **argv)
    9129             : {
    9130          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9131          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9132             :     {
    9133           2 :         sqlite3_result_int(pContext, 0);
    9134           2 :         return;
    9135             :     }
    9136             : 
    9137             :     const char *pszTableName =
    9138          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9139             :     const char *pszGeomName =
    9140          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9141             :     GDALGeoPackageDataset *poDS =
    9142          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9143             : 
    9144          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9145          10 :         poDS->GetLayerByName(pszTableName));
    9146          10 :     if (poLyr == nullptr)
    9147             :     {
    9148           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9149           1 :         sqlite3_result_int(pContext, 0);
    9150           1 :         return;
    9151             :     }
    9152           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9153             :     {
    9154           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9155           1 :         sqlite3_result_int(pContext, 0);
    9156           1 :         return;
    9157             :     }
    9158             : 
    9159           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    9160             : }
    9161             : 
    9162             : /************************************************************************/
    9163             : /*                  OGRGeoPackageHasSpatialIndex()                      */
    9164             : /************************************************************************/
    9165             : 
    9166          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    9167             :                                          int /*argc*/, sqlite3_value **argv)
    9168             : {
    9169          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9170          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9171             :     {
    9172           2 :         sqlite3_result_int(pContext, 0);
    9173           2 :         return;
    9174             :     }
    9175             : 
    9176             :     const char *pszTableName =
    9177          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9178             :     const char *pszGeomName =
    9179          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9180             :     GDALGeoPackageDataset *poDS =
    9181          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9182             : 
    9183          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    9184          27 :         poDS->GetLayerByName(pszTableName));
    9185          27 :     if (poLyr == nullptr)
    9186             :     {
    9187           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    9188           1 :         sqlite3_result_int(pContext, 0);
    9189           1 :         return;
    9190             :     }
    9191          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    9192             :     {
    9193           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    9194           1 :         sqlite3_result_int(pContext, 0);
    9195           1 :         return;
    9196             :     }
    9197             : 
    9198          25 :     poLyr->RunDeferredCreationIfNecessary();
    9199          25 :     poLyr->CreateSpatialIndexIfNecessary();
    9200             : 
    9201          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    9202             : }
    9203             : 
    9204             : /************************************************************************/
    9205             : /*                       GPKG_hstore_get_value()                        */
    9206             : /************************************************************************/
    9207             : 
    9208           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    9209             :                                   sqlite3_value **argv)
    9210             : {
    9211           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9212           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    9213             :     {
    9214           2 :         sqlite3_result_null(pContext);
    9215           2 :         return;
    9216             :     }
    9217             : 
    9218             :     const char *pszHStore =
    9219           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9220             :     const char *pszSearchedKey =
    9221           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    9222           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    9223           2 :     if (pszValue != nullptr)
    9224           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    9225             :     else
    9226           1 :         sqlite3_result_null(pContext);
    9227             : }
    9228             : 
    9229             : /************************************************************************/
    9230             : /*                      GPKG_GDAL_GetMemFileFromBlob()                  */
    9231             : /************************************************************************/
    9232             : 
    9233         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    9234             : {
    9235         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    9236             :     const GByte *pabyBLOB =
    9237         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    9238             :     const CPLString osMemFileName(
    9239         105 :         VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
    9240         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    9241             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    9242         105 :     VSIFCloseL(fp);
    9243         105 :     return osMemFileName;
    9244             : }
    9245             : 
    9246             : /************************************************************************/
    9247             : /*                       GPKG_GDAL_GetMimeType()                        */
    9248             : /************************************************************************/
    9249             : 
    9250          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    9251             :                                   sqlite3_value **argv)
    9252             : {
    9253          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9254             :     {
    9255           0 :         sqlite3_result_null(pContext);
    9256           0 :         return;
    9257             :     }
    9258             : 
    9259          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9260             :     GDALDriver *poDriver =
    9261          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    9262          35 :     if (poDriver != nullptr)
    9263             :     {
    9264          35 :         const char *pszRes = nullptr;
    9265          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    9266          23 :             pszRes = "image/png";
    9267          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    9268           6 :             pszRes = "image/jpeg";
    9269           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    9270           6 :             pszRes = "image/x-webp";
    9271           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    9272           0 :             pszRes = "image/tiff";
    9273             :         else
    9274           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    9275          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    9276             :     }
    9277             :     else
    9278           0 :         sqlite3_result_null(pContext);
    9279          35 :     VSIUnlink(osMemFileName);
    9280             : }
    9281             : 
    9282             : /************************************************************************/
    9283             : /*                       GPKG_GDAL_GetBandCount()                       */
    9284             : /************************************************************************/
    9285             : 
    9286          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    9287             :                                    sqlite3_value **argv)
    9288             : {
    9289          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9290             :     {
    9291           0 :         sqlite3_result_null(pContext);
    9292           0 :         return;
    9293             :     }
    9294             : 
    9295          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9296             :     auto poDS = std::unique_ptr<GDALDataset>(
    9297             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9298          70 :                           nullptr, nullptr, nullptr));
    9299          35 :     if (poDS != nullptr)
    9300             :     {
    9301          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    9302             :     }
    9303             :     else
    9304           0 :         sqlite3_result_null(pContext);
    9305          35 :     VSIUnlink(osMemFileName);
    9306             : }
    9307             : 
    9308             : /************************************************************************/
    9309             : /*                       GPKG_GDAL_HasColorTable()                      */
    9310             : /************************************************************************/
    9311             : 
    9312          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    9313             :                                     sqlite3_value **argv)
    9314             : {
    9315          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    9316             :     {
    9317           0 :         sqlite3_result_null(pContext);
    9318           0 :         return;
    9319             :     }
    9320             : 
    9321          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    9322             :     auto poDS = std::unique_ptr<GDALDataset>(
    9323             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    9324          70 :                           nullptr, nullptr, nullptr));
    9325          35 :     if (poDS != nullptr)
    9326             :     {
    9327          35 :         sqlite3_result_int(
    9328          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9329          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9330             :     }
    9331             :     else
    9332           0 :         sqlite3_result_null(pContext);
    9333          35 :     VSIUnlink(osMemFileName);
    9334             : }
    9335             : 
    9336             : /************************************************************************/
    9337             : /*                      GetRasterLayerDataset()                         */
    9338             : /************************************************************************/
    9339             : 
    9340             : GDALDataset *
    9341          12 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9342             : {
    9343          12 :     const auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9344          12 :     if (oIter != m_oCachedRasterDS.end())
    9345          10 :         return oIter->second.get();
    9346             : 
    9347             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9348           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9349           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9350           2 :     if (!poDS)
    9351             :     {
    9352           0 :         return nullptr;
    9353             :     }
    9354           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9355           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9356             : }
    9357             : 
    9358             : /************************************************************************/
    9359             : /*                   GPKG_gdal_get_layer_pixel_value()                  */
    9360             : /************************************************************************/
    9361             : 
    9362             : // NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
    9363             : // and ogrgeopackagedatasource.cpp
    9364          13 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
    9365             :                                             sqlite3_value **argv)
    9366             : {
    9367          13 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9368             :     {
    9369           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9370             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9371           1 :         sqlite3_result_null(pContext);
    9372           1 :         return;
    9373             :     }
    9374             : 
    9375             :     const char *pszLayerName =
    9376          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9377             : 
    9378             :     GDALGeoPackageDataset *poGlobalDS =
    9379          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9380          12 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9381          12 :     if (!poDS)
    9382             :     {
    9383           0 :         sqlite3_result_null(pContext);
    9384           0 :         return;
    9385             :     }
    9386             : 
    9387          12 :     OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
    9388             :                                           pContext, argc, argv, poDS);
    9389             : }
    9390             : 
    9391             : /************************************************************************/
    9392             : /*                       GPKG_ogr_layer_Extent()                        */
    9393             : /************************************************************************/
    9394             : 
    9395           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9396             :                                   sqlite3_value **argv)
    9397             : {
    9398           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9399             :     {
    9400           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9401             :                  "ogr_layer_Extent");
    9402           1 :         sqlite3_result_null(pContext);
    9403           2 :         return;
    9404             :     }
    9405             : 
    9406             :     const char *pszLayerName =
    9407           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9408             :     GDALGeoPackageDataset *poDS =
    9409           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9410           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9411           2 :     if (!poLayer)
    9412             :     {
    9413           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9414             :                  "ogr_layer_Extent");
    9415           1 :         sqlite3_result_null(pContext);
    9416           1 :         return;
    9417             :     }
    9418             : 
    9419           1 :     if (poLayer->GetGeomType() == wkbNone)
    9420             :     {
    9421           0 :         sqlite3_result_null(pContext);
    9422           0 :         return;
    9423             :     }
    9424             : 
    9425           1 :     OGREnvelope sExtent;
    9426           1 :     if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
    9427             :     {
    9428           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9429             :                  "ogr_layer_Extent");
    9430           0 :         sqlite3_result_null(pContext);
    9431           0 :         return;
    9432             :     }
    9433             : 
    9434           1 :     OGRPolygon oPoly;
    9435           1 :     OGRLinearRing *poRing = new OGRLinearRing();
    9436           1 :     oPoly.addRingDirectly(poRing);
    9437           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9438           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9439           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9440           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9441           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9442             : 
    9443           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9444           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9445           1 :     size_t nBLOBDestLen = 0;
    9446             :     GByte *pabyDestBLOB =
    9447           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9448           1 :     if (!pabyDestBLOB)
    9449             :     {
    9450           0 :         sqlite3_result_null(pContext);
    9451           0 :         return;
    9452             :     }
    9453           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9454             :                         VSIFree);
    9455             : }
    9456             : 
    9457             : /************************************************************************/
    9458             : /*                      InstallSQLFunctions()                           */
    9459             : /************************************************************************/
    9460             : 
    9461             : #ifndef SQLITE_DETERMINISTIC
    9462             : #define SQLITE_DETERMINISTIC 0
    9463             : #endif
    9464             : 
    9465             : #ifndef SQLITE_INNOCUOUS
    9466             : #define SQLITE_INNOCUOUS 0
    9467             : #endif
    9468             : 
    9469             : #ifndef UTF8_INNOCUOUS
    9470             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9471             : #endif
    9472             : 
    9473        1820 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9474             : {
    9475        1820 :     InitSpatialite();
    9476             : 
    9477             :     // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
    9478             :     // that take geometries will accept GPKG encoded geometries without
    9479             :     // explicit conversion.
    9480             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9481             :     // error.
    9482        1820 :     sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
    9483             :                  nullptr);
    9484             : 
    9485             :     /* Used by RTree Spatial Index Extension */
    9486        1820 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9487             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9488        1820 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9489             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9490        1820 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9491             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9492        1820 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9493             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9494        1820 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9495             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9496             : 
    9497             :     /* Used by Geometry Type Triggers Extension */
    9498        1820 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9499             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9500        1820 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9501             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9502             :                             nullptr);
    9503             : 
    9504             :     /* Used by Geometry SRS ID Triggers Extension */
    9505        1820 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9506             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9507             : 
    9508             :     /* Spatialite-like functions */
    9509        1820 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9510             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9511        1820 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9512             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9513        1820 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9514             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9515             : 
    9516             :     // HSTORE functions
    9517        1820 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9518             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9519             : 
    9520             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9521        1820 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9522             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9523        1820 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9524             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9525        1820 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9526             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9527             : 
    9528        1820 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9529             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9530             :                             nullptr, nullptr);
    9531        1820 :     sqlite3_create_function(
    9532             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9533             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9534             : 
    9535        1820 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9536             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9537             :                             nullptr);
    9538        1820 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9539             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9540             :                             nullptr, nullptr);
    9541             : 
    9542             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9543        1820 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9544             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9545             : 
    9546             :     // GDAL specific function
    9547        1820 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9548             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9549             : 
    9550             :     // May be used by ogrmerge.py
    9551        1820 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9552             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9553             :                             nullptr, nullptr);
    9554             : 
    9555        1820 :     if (OGRGeometryFactory::haveGEOS())
    9556             :     {
    9557        1820 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9558             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9559             :     }
    9560             : 
    9561        1820 :     sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
    9562             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9563             :                             nullptr);
    9564        1820 :     sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
    9565             :                             OGRGeoPackageLengthOrGeodesicLength, nullptr,
    9566             :                             nullptr);
    9567             : 
    9568        1820 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9569             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9570        1820 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9571             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9572             : 
    9573             :     // Debug functions
    9574        1820 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9575             :     {
    9576         417 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9577             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9578             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9579         417 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9580             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9581             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9582         417 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9583             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9584             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9585             :     }
    9586             : 
    9587        1820 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9588             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9589             :                             nullptr);
    9590        1820 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
    9591             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9592             :                             nullptr);
    9593             : 
    9594             :     // Function from VirtualOGR
    9595        1820 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9596             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9597             : 
    9598        1820 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9599        1820 : }
    9600             : 
    9601             : /************************************************************************/
    9602             : /*                         OpenOrCreateDB()                             */
    9603             : /************************************************************************/
    9604             : 
    9605        1821 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9606             : {
    9607        1821 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9608             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9609             :         /*bLoadExtensions=*/true);
    9610        1821 :     if (!bSuccess)
    9611           6 :         return false;
    9612             : 
    9613             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9614             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9615             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9616        1815 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9617             : 
    9618        1815 :     InstallSQLFunctions();
    9619             : 
    9620             :     const char *pszSqlitePragma =
    9621        1815 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9622        1815 :     OGRErr eErr = OGRERR_NONE;
    9623           6 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9624             :         // Older sqlite versions don't have this pragma
    9625        3636 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9626        1815 :         eErr == OGRERR_NONE)
    9627             :     {
    9628        1815 :         bool bNeedsTrustedSchema = false;
    9629             : 
    9630             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9631             :         // able to use the RTree from triggers, which is only needed when
    9632             :         // modifying the RTree.
    9633        4489 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9634        2771 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9635         956 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9636             :         {
    9637         956 :             bNeedsTrustedSchema = true;
    9638             :         }
    9639             : 
    9640             : #ifdef HAVE_SPATIALITE
    9641             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9642         859 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9643         778 :             SQLGetInteger(
    9644             :                 hDB,
    9645             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9646             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9647           1 :                 nullptr) == 1 &&
    9648        2674 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9649             :         {
    9650           1 :             bNeedsTrustedSchema = true;
    9651             :         }
    9652             : #endif
    9653             : 
    9654        1815 :         if (bNeedsTrustedSchema)
    9655             :         {
    9656         957 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9657         957 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9658             :         }
    9659             :     }
    9660             : 
    9661        1815 :     return true;
    9662             : }
    9663             : 
    9664             : /************************************************************************/
    9665             : /*                   GetLayerWithGetSpatialWhereByName()                */
    9666             : /************************************************************************/
    9667             : 
    9668             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9669          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9670             : {
    9671             :     OGRGeoPackageLayer *poRet =
    9672          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9673          90 :     return std::pair(poRet, poRet);
    9674             : }
    9675             : 
    9676             : /************************************************************************/
    9677             : /*                       CommitTransaction()                            */
    9678             : /************************************************************************/
    9679             : 
    9680         155 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9681             : 
    9682             : {
    9683         155 :     if (nSoftTransactionLevel == 1)
    9684             :     {
    9685         154 :         FlushMetadata();
    9686         346 :         for (int i = 0; i < m_nLayers; i++)
    9687             :         {
    9688         192 :             m_papoLayers[i]->DoJobAtTransactionCommit();
    9689             :         }
    9690             :     }
    9691             : 
    9692         155 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9693             : }
    9694             : 
    9695             : /************************************************************************/
    9696             : /*                     RollbackTransaction()                            */
    9697             : /************************************************************************/
    9698             : 
    9699          29 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9700             : 
    9701             : {
    9702             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9703          58 :     std::vector<bool> abAddTriggers;
    9704          29 :     std::vector<bool> abTriggersDeletedInTransaction;
    9705             : #endif
    9706          29 :     if (nSoftTransactionLevel == 1)
    9707             :     {
    9708          28 :         FlushMetadata();
    9709          58 :         for (int i = 0; i < m_nLayers; i++)
    9710             :         {
    9711             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9712          30 :             abAddTriggers.push_back(
    9713          30 :                 m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
    9714          30 :             abTriggersDeletedInTransaction.push_back(
    9715          30 :                 m_papoLayers[i]
    9716          30 :                     ->GetOGRFeatureCountTriggersDeletedInTransaction());
    9717          30 :             m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
    9718             : #endif
    9719          30 :             m_papoLayers[i]->DoJobAtTransactionRollback();
    9720             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9721          30 :             m_papoLayers[i]->DisableFeatureCount();
    9722             : #endif
    9723             :         }
    9724             :     }
    9725             : 
    9726          29 :     OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9727             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9728          29 :     if (!abAddTriggers.empty())
    9729             :     {
    9730          56 :         for (int i = 0; i < m_nLayers; i++)
    9731             :         {
    9732          30 :             if (abTriggersDeletedInTransaction[i])
    9733             :             {
    9734           7 :                 m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
    9735             :             }
    9736             :             else
    9737             :             {
    9738          23 :                 m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
    9739          46 :                     abAddTriggers[i]);
    9740             :             }
    9741             :         }
    9742             :     }
    9743             : #endif
    9744          58 :     return eErr;
    9745             : }
    9746             : 
    9747             : /************************************************************************/
    9748             : /*                       GetGeometryTypeString()                        */
    9749             : /************************************************************************/
    9750             : 
    9751             : const char *
    9752        1243 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9753             : {
    9754        1243 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9755        1255 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9756          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9757             :     {
    9758           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9759             :     }
    9760        1243 :     return pszGPKGGeomType;
    9761             : }
    9762             : 
    9763             : /************************************************************************/
    9764             : /*                           GetFieldDomainNames()                      */
    9765             : /************************************************************************/
    9766             : 
    9767             : std::vector<std::string>
    9768          10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9769             : {
    9770          10 :     if (!HasDataColumnConstraintsTable())
    9771           3 :         return std::vector<std::string>();
    9772             : 
    9773          14 :     std::vector<std::string> oDomainNamesList;
    9774             : 
    9775           7 :     std::unique_ptr<SQLResult> oResultTable;
    9776             :     {
    9777             :         std::string osSQL =
    9778             :             "SELECT DISTINCT constraint_name "
    9779             :             "FROM gpkg_data_column_constraints "
    9780             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9781             :             "ORDER BY constraint_name "
    9782           7 :             "LIMIT 10000"  // to avoid denial of service
    9783             :             ;
    9784           7 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9785           7 :         if (!oResultTable)
    9786           0 :             return oDomainNamesList;
    9787             :     }
    9788             : 
    9789           7 :     if (oResultTable->RowCount() == 10000)
    9790             :     {
    9791           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9792             :                  "Number of rows returned for field domain names has been "
    9793             :                  "truncated.");
    9794             :     }
    9795           7 :     else if (oResultTable->RowCount() > 0)
    9796             :     {
    9797           7 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9798          89 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9799             :         {
    9800          82 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9801          82 :             if (!pszConstraintName)
    9802           0 :                 continue;
    9803             : 
    9804          82 :             oDomainNamesList.emplace_back(pszConstraintName);
    9805             :         }
    9806             :     }
    9807             : 
    9808           7 :     return oDomainNamesList;
    9809             : }
    9810             : 
    9811             : /************************************************************************/
    9812             : /*                           GetFieldDomain()                           */
    9813             : /************************************************************************/
    9814             : 
    9815             : const OGRFieldDomain *
    9816         102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9817             : {
    9818         102 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9819         102 :     if (baseRet)
    9820          42 :         return baseRet;
    9821             : 
    9822          60 :     if (!HasDataColumnConstraintsTable())
    9823           4 :         return nullptr;
    9824             : 
    9825          56 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9826          56 :     const char *min_is_inclusive =
    9827          56 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9828          56 :     const char *max_is_inclusive =
    9829          56 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9830             : 
    9831          56 :     std::unique_ptr<SQLResult> oResultTable;
    9832             :     // Note: for coded domains, we use a little trick by using a dummy
    9833             :     // _{domainname}_domain_description enum that has a single entry whose
    9834             :     // description is the description of the main domain.
    9835             :     {
    9836          56 :         char *pszSQL = sqlite3_mprintf(
    9837             :             "SELECT constraint_type, value, min, %s, "
    9838             :             "max, %s, description, constraint_name "
    9839             :             "FROM gpkg_data_column_constraints "
    9840             :             "WHERE constraint_name IN ('%q', "
    9841             :             "'_%q_domain_description') "
    9842             :             "AND length(constraint_type) < 100 "  // to
    9843             :                                                   // avoid
    9844             :                                                   // denial
    9845             :                                                   // of
    9846             :                                                   // service
    9847             :             "AND (value IS NULL OR length(value) < "
    9848             :             "10000) "  // to avoid denial
    9849             :                        // of service
    9850             :             "AND (description IS NULL OR "
    9851             :             "length(description) < 10000) "  // to
    9852             :                                              // avoid
    9853             :                                              // denial
    9854             :                                              // of
    9855             :                                              // service
    9856             :             "ORDER BY value "
    9857             :             "LIMIT 10000",  // to avoid denial of
    9858             :                             // service
    9859             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9860          56 :         oResultTable = SQLQuery(hDB, pszSQL);
    9861          56 :         sqlite3_free(pszSQL);
    9862          56 :         if (!oResultTable)
    9863           0 :             return nullptr;
    9864             :     }
    9865          56 :     if (oResultTable->RowCount() == 0)
    9866             :     {
    9867          15 :         return nullptr;
    9868             :     }
    9869          41 :     if (oResultTable->RowCount() == 10000)
    9870             :     {
    9871           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9872             :                  "Number of rows returned for field domain %s has been "
    9873             :                  "truncated.",
    9874             :                  name.c_str());
    9875             :     }
    9876             : 
    9877             :     // Try to find the field domain data type from fields that implement it
    9878          41 :     int nFieldType = -1;
    9879          41 :     OGRFieldSubType eSubType = OFSTNone;
    9880          41 :     if (HasDataColumnsTable())
    9881             :     {
    9882          36 :         char *pszSQL = sqlite3_mprintf(
    9883             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9884             :             "constraint_name = '%q' LIMIT 10",
    9885             :             name.c_str());
    9886          72 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9887          36 :         sqlite3_free(pszSQL);
    9888          36 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9889             :         {
    9890          46 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9891             :                  iRecord++)
    9892             :             {
    9893          23 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9894          23 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9895          23 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9896           0 :                     continue;
    9897             :                 OGRLayer *poLayer =
    9898          46 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9899          23 :                         pszTableName);
    9900          23 :                 if (poLayer)
    9901             :                 {
    9902          23 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9903          23 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9904          23 :                     if (nIdx >= 0)
    9905             :                     {
    9906          23 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9907          23 :                         const auto eType = poFieldDefn->GetType();
    9908          23 :                         if (nFieldType < 0)
    9909             :                         {
    9910          23 :                             nFieldType = eType;
    9911          23 :                             eSubType = poFieldDefn->GetSubType();
    9912             :                         }
    9913           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9914             :                                  nFieldType == OFTInteger)
    9915             :                         {
    9916             :                             // ok
    9917             :                         }
    9918           0 :                         else if (eType == OFTInteger &&
    9919           0 :                                  (nFieldType == OFTInteger64 ||
    9920             :                                   nFieldType == OFTReal))
    9921             :                         {
    9922           0 :                             nFieldType = OFTInteger;
    9923           0 :                             eSubType = OFSTNone;
    9924             :                         }
    9925           0 :                         else if (nFieldType != eType)
    9926             :                         {
    9927           0 :                             nFieldType = -1;
    9928           0 :                             eSubType = OFSTNone;
    9929           0 :                             break;
    9930             :                         }
    9931             :                     }
    9932             :                 }
    9933             :             }
    9934             :         }
    9935             :     }
    9936             : 
    9937          41 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9938          82 :     std::vector<OGRCodedValue> asValues;
    9939          41 :     bool error = false;
    9940          82 :     CPLString osLastConstraintType;
    9941          41 :     int nFieldTypeFromEnumCode = -1;
    9942          82 :     std::string osConstraintDescription;
    9943          82 :     std::string osDescrConstraintName("_");
    9944          41 :     osDescrConstraintName += name;
    9945          41 :     osDescrConstraintName += "_domain_description";
    9946         100 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
    9947             :     {
    9948          63 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
    9949          63 :         if (pszConstraintType == nullptr)
    9950           0 :             continue;
    9951          63 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
    9952          63 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
    9953             :         const bool bIsMinIncluded =
    9954          63 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
    9955          63 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
    9956             :         const bool bIsMaxIncluded =
    9957          63 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
    9958          63 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
    9959          63 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
    9960             : 
    9961          63 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
    9962             :         {
    9963           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9964             :                      "Only constraint of type 'enum' can have multiple rows");
    9965           1 :             error = true;
    9966           1 :             break;
    9967             :         }
    9968             : 
    9969          62 :         if (strcmp(pszConstraintType, "enum") == 0)
    9970             :         {
    9971          42 :             if (pszValue == nullptr)
    9972             :             {
    9973           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9974             :                          "NULL in 'value' column of enumeration");
    9975           1 :                 error = true;
    9976           1 :                 break;
    9977             :             }
    9978          41 :             if (osDescrConstraintName == pszConstraintName)
    9979             :             {
    9980           1 :                 if (pszDescription)
    9981             :                 {
    9982           1 :                     osConstraintDescription = pszDescription;
    9983             :                 }
    9984           1 :                 continue;
    9985             :             }
    9986          40 :             if (asValues.empty())
    9987             :             {
    9988          20 :                 asValues.reserve(oResultTable->RowCount() + 1);
    9989             :             }
    9990             :             OGRCodedValue cv;
    9991             :             // intended: the 'value' column in GPKG is actually the code
    9992          40 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
    9993          40 :             if (cv.pszCode == nullptr)
    9994             :             {
    9995           0 :                 error = true;
    9996           0 :                 break;
    9997             :             }
    9998          40 :             if (pszDescription)
    9999             :             {
   10000          29 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
   10001          29 :                 if (cv.pszValue == nullptr)
   10002             :                 {
   10003           0 :                     VSIFree(cv.pszCode);
   10004           0 :                     error = true;
   10005           0 :                     break;
   10006             :                 }
   10007             :             }
   10008             :             else
   10009             :             {
   10010          11 :                 cv.pszValue = nullptr;
   10011             :             }
   10012             : 
   10013             :             // If we can't get the data type from field definition, guess it
   10014             :             // from code.
   10015          40 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
   10016             :             {
   10017          18 :                 switch (CPLGetValueType(cv.pszCode))
   10018             :                 {
   10019          13 :                     case CPL_VALUE_INTEGER:
   10020             :                     {
   10021          13 :                         if (nFieldTypeFromEnumCode != OFTReal &&
   10022             :                             nFieldTypeFromEnumCode != OFTInteger64)
   10023             :                         {
   10024           9 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
   10025          17 :                             if (nVal < std::numeric_limits<int>::min() ||
   10026           8 :                                 nVal > std::numeric_limits<int>::max())
   10027             :                             {
   10028           3 :                                 nFieldTypeFromEnumCode = OFTInteger64;
   10029             :                             }
   10030             :                             else
   10031             :                             {
   10032           6 :                                 nFieldTypeFromEnumCode = OFTInteger;
   10033             :                             }
   10034             :                         }
   10035          13 :                         break;
   10036             :                     }
   10037             : 
   10038           3 :                     case CPL_VALUE_REAL:
   10039           3 :                         nFieldTypeFromEnumCode = OFTReal;
   10040           3 :                         break;
   10041             : 
   10042           2 :                     case CPL_VALUE_STRING:
   10043           2 :                         nFieldTypeFromEnumCode = OFTString;
   10044           2 :                         break;
   10045             :                 }
   10046             :             }
   10047             : 
   10048          40 :             asValues.emplace_back(cv);
   10049             :         }
   10050          20 :         else if (strcmp(pszConstraintType, "range") == 0)
   10051             :         {
   10052             :             OGRField sMin;
   10053             :             OGRField sMax;
   10054          14 :             OGR_RawField_SetUnset(&sMin);
   10055          14 :             OGR_RawField_SetUnset(&sMax);
   10056          14 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
   10057           8 :                 nFieldType = OFTReal;
   10058          27 :             if (pszMin != nullptr &&
   10059          13 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
   10060             :             {
   10061          10 :                 if (nFieldType == OFTInteger)
   10062           3 :                     sMin.Integer = atoi(pszMin);
   10063           7 :                 else if (nFieldType == OFTInteger64)
   10064           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
   10065             :                 else /* if( nFieldType == OFTReal ) */
   10066           4 :                     sMin.Real = CPLAtof(pszMin);
   10067             :             }
   10068          27 :             if (pszMax != nullptr &&
   10069          13 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
   10070             :             {
   10071          10 :                 if (nFieldType == OFTInteger)
   10072           3 :                     sMax.Integer = atoi(pszMax);
   10073           7 :                 else if (nFieldType == OFTInteger64)
   10074           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
   10075             :                 else /* if( nFieldType == OFTReal ) */
   10076           4 :                     sMax.Real = CPLAtof(pszMax);
   10077             :             }
   10078          28 :             poDomain.reset(new OGRRangeFieldDomain(
   10079             :                 name, pszDescription ? pszDescription : "",
   10080             :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
   10081          14 :                 bIsMinIncluded, sMax, bIsMaxIncluded));
   10082             :         }
   10083           6 :         else if (strcmp(pszConstraintType, "glob") == 0)
   10084             :         {
   10085           5 :             if (pszValue == nullptr)
   10086             :             {
   10087           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10088             :                          "NULL in 'value' column of glob");
   10089           1 :                 error = true;
   10090           1 :                 break;
   10091             :             }
   10092           4 :             if (nFieldType < 0)
   10093           1 :                 nFieldType = OFTString;
   10094           8 :             poDomain.reset(new OGRGlobFieldDomain(
   10095             :                 name, pszDescription ? pszDescription : "",
   10096           4 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
   10097             :         }
   10098             :         else
   10099             :         {
   10100           1 :             CPLError(CE_Failure, CPLE_AppDefined,
   10101             :                      "Unhandled constraint_type: %s", pszConstraintType);
   10102           1 :             error = true;
   10103           1 :             break;
   10104             :         }
   10105             : 
   10106          58 :         osLastConstraintType = pszConstraintType;
   10107             :     }
   10108             : 
   10109          41 :     if (!asValues.empty())
   10110             :     {
   10111          20 :         if (nFieldType < 0)
   10112           9 :             nFieldType = nFieldTypeFromEnumCode;
   10113          20 :         poDomain.reset(
   10114             :             new OGRCodedFieldDomain(name, osConstraintDescription,
   10115             :                                     static_cast<OGRFieldType>(nFieldType),
   10116          20 :                                     eSubType, std::move(asValues)));
   10117             :     }
   10118             : 
   10119          41 :     if (error)
   10120             :     {
   10121           4 :         return nullptr;
   10122             :     }
   10123             : 
   10124          37 :     m_oMapFieldDomains[name] = std::move(poDomain);
   10125          37 :     return GDALDataset::GetFieldDomain(name);
   10126             : }
   10127             : 
   10128             : /************************************************************************/
   10129             : /*                           AddFieldDomain()                           */
   10130             : /************************************************************************/
   10131             : 
   10132          18 : bool GDALGeoPackageDataset::AddFieldDomain(
   10133             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
   10134             : {
   10135          36 :     const std::string domainName(domain->GetName());
   10136          18 :     if (!GetUpdate())
   10137             :     {
   10138           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10139             :                  "AddFieldDomain() not supported on read-only dataset");
   10140           0 :         return false;
   10141             :     }
   10142          18 :     if (GetFieldDomain(domainName) != nullptr)
   10143             :     {
   10144           1 :         failureReason = "A domain of identical name already exists";
   10145           1 :         return false;
   10146             :     }
   10147          17 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
   10148           0 :         return false;
   10149             : 
   10150          17 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
   10151          17 :     const char *min_is_inclusive =
   10152          17 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
   10153          17 :     const char *max_is_inclusive =
   10154          17 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
   10155             : 
   10156          17 :     const auto &osDescription = domain->GetDescription();
   10157          17 :     switch (domain->GetDomainType())
   10158             :     {
   10159          11 :         case OFDT_CODED:
   10160             :         {
   10161             :             const auto poCodedDomain =
   10162          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
   10163          11 :             if (!osDescription.empty())
   10164             :             {
   10165             :                 // We use a little trick by using a dummy
   10166             :                 // _{domainname}_domain_description enum that has a single
   10167             :                 // entry whose description is the description of the main
   10168             :                 // domain.
   10169           1 :                 char *pszSQL = sqlite3_mprintf(
   10170             :                     "INSERT INTO gpkg_data_column_constraints ("
   10171             :                     "constraint_name, constraint_type, value, "
   10172             :                     "min, %s, max, %s, "
   10173             :                     "description) VALUES ("
   10174             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
   10175             :                     "NULL, %Q)",
   10176             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10177             :                     osDescription.c_str());
   10178           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
   10179           1 :                 sqlite3_free(pszSQL);
   10180             :             }
   10181          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
   10182          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
   10183             :             {
   10184          22 :                 char *pszSQL = sqlite3_mprintf(
   10185             :                     "INSERT INTO gpkg_data_column_constraints ("
   10186             :                     "constraint_name, constraint_type, value, "
   10187             :                     "min, %s, max, %s, "
   10188             :                     "description) VALUES ("
   10189             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
   10190             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10191          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
   10192          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10193          22 :                 sqlite3_free(pszSQL);
   10194          22 :                 if (!ok)
   10195           0 :                     return false;
   10196             :             }
   10197          11 :             break;
   10198             :         }
   10199             : 
   10200           5 :         case OFDT_RANGE:
   10201             :         {
   10202             :             const auto poRangeDomain =
   10203           5 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
   10204           5 :             const auto eFieldType = poRangeDomain->GetFieldType();
   10205           5 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
   10206             :                 eFieldType != OFTReal)
   10207             :             {
   10208             :                 failureReason = "Only range domains of numeric type are "
   10209           0 :                                 "supported in GeoPackage";
   10210           0 :                 return false;
   10211             :             }
   10212             : 
   10213           5 :             double dfMin = -std::numeric_limits<double>::infinity();
   10214           5 :             double dfMax = std::numeric_limits<double>::infinity();
   10215           5 :             bool bMinIsInclusive = true;
   10216           5 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
   10217           5 :             bool bMaxIsInclusive = true;
   10218           5 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
   10219           5 :             if (eFieldType == OFTInteger)
   10220             :             {
   10221           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10222           1 :                     dfMin = sMin.Integer;
   10223           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10224           1 :                     dfMax = sMax.Integer;
   10225             :             }
   10226           4 :             else if (eFieldType == OFTInteger64)
   10227             :             {
   10228           1 :                 if (!OGR_RawField_IsUnset(&sMin))
   10229           1 :                     dfMin = static_cast<double>(sMin.Integer64);
   10230           1 :                 if (!OGR_RawField_IsUnset(&sMax))
   10231           1 :                     dfMax = static_cast<double>(sMax.Integer64);
   10232             :             }
   10233             :             else /* if( eFieldType == OFTReal ) */
   10234             :             {
   10235           3 :                 if (!OGR_RawField_IsUnset(&sMin))
   10236           3 :                     dfMin = sMin.Real;
   10237           3 :                 if (!OGR_RawField_IsUnset(&sMax))
   10238           3 :                     dfMax = sMax.Real;
   10239             :             }
   10240             : 
   10241           5 :             sqlite3_stmt *hInsertStmt = nullptr;
   10242             :             const char *pszSQL =
   10243           5 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
   10244             :                            "constraint_name, constraint_type, value, "
   10245             :                            "min, %s, max, %s, "
   10246             :                            "description) VALUES ("
   10247             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
   10248             :                            min_is_inclusive, max_is_inclusive);
   10249           5 :             if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
   10250             :                 SQLITE_OK)
   10251             :             {
   10252           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10253             :                          "failed to prepare SQL: %s", pszSQL);
   10254           0 :                 return false;
   10255             :             }
   10256           5 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
   10257           5 :                               static_cast<int>(domainName.size()),
   10258             :                               SQLITE_TRANSIENT);
   10259           5 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
   10260           5 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
   10261           5 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
   10262           5 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10263           5 :             if (osDescription.empty())
   10264             :             {
   10265           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10266             :             }
   10267             :             else
   10268             :             {
   10269           2 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10270           2 :                                   static_cast<int>(osDescription.size()),
   10271             :                                   SQLITE_TRANSIENT);
   10272             :             }
   10273           5 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10274           5 :             sqlite3_finalize(hInsertStmt);
   10275           5 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10276             :             {
   10277           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10278             :                          "failed to execute insertion: %s",
   10279             :                          sqlite3_errmsg(hDB));
   10280           0 :                 return false;
   10281             :             }
   10282             : 
   10283           5 :             break;
   10284             :         }
   10285             : 
   10286           1 :         case OFDT_GLOB:
   10287             :         {
   10288             :             const auto poGlobDomain =
   10289           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10290           2 :             char *pszSQL = sqlite3_mprintf(
   10291             :                 "INSERT INTO gpkg_data_column_constraints ("
   10292             :                 "constraint_name, constraint_type, value, "
   10293             :                 "min, %s, max, %s, "
   10294             :                 "description) VALUES ("
   10295             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10296             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10297           1 :                 poGlobDomain->GetGlob().c_str(),
   10298           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10299           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10300           1 :             sqlite3_free(pszSQL);
   10301           1 :             if (!ok)
   10302           0 :                 return false;
   10303             : 
   10304           1 :             break;
   10305             :         }
   10306             :     }
   10307             : 
   10308          17 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10309          17 :     return true;
   10310             : }
   10311             : 
   10312             : /************************************************************************/
   10313             : /*                          AddRelationship()                           */
   10314             : /************************************************************************/
   10315             : 
   10316          20 : bool GDALGeoPackageDataset::AddRelationship(
   10317             :     std::unique_ptr<GDALRelationship> &&relationship,
   10318             :     std::string &failureReason)
   10319             : {
   10320          20 :     if (!GetUpdate())
   10321             :     {
   10322           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10323             :                  "AddRelationship() not supported on read-only dataset");
   10324           0 :         return false;
   10325             :     }
   10326             : 
   10327             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10328          20 :         relationship->GetLeftTableName().c_str(),
   10329          20 :         relationship->GetRightTableName().c_str(),
   10330          80 :         relationship->GetRelatedTableType().c_str());
   10331             :     // sanity checks
   10332          20 :     if (GetRelationship(osRelationshipName) != nullptr)
   10333             :     {
   10334           1 :         failureReason = "A relationship of identical name already exists";
   10335           1 :         return false;
   10336             :     }
   10337             : 
   10338          19 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10339             :     {
   10340          12 :         return false;
   10341             :     }
   10342             : 
   10343           7 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10344             :     {
   10345           0 :         return false;
   10346             :     }
   10347           7 :     if (!CreateRelationsTableIfNecessary())
   10348             :     {
   10349           0 :         failureReason = "Could not create gpkgext_relations table";
   10350           0 :         return false;
   10351             :     }
   10352           7 :     if (SQLGetInteger(GetDB(),
   10353             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10354             :                       "table_name = 'gpkgext_relations'",
   10355           7 :                       nullptr) != 1)
   10356             :     {
   10357           2 :         if (OGRERR_NONE !=
   10358           2 :             SQLCommand(
   10359             :                 GetDB(),
   10360             :                 "INSERT INTO gpkg_extensions "
   10361             :                 "(table_name,column_name,extension_name,definition,scope) "
   10362             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10363             :                 "'http://www.geopackage.org/18-000.html', "
   10364             :                 "'read-write')"))
   10365             :         {
   10366             :             failureReason =
   10367           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10368           0 :             return false;
   10369             :         }
   10370             :     }
   10371             : 
   10372           7 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10373           7 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10374           7 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10375           7 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10376             : 
   10377          14 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10378           7 :     if (osRelatedTableType.empty())
   10379             :     {
   10380           5 :         osRelatedTableType = "features";
   10381             :     }
   10382             : 
   10383             :     // generate mapping table if not set
   10384          14 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10385           7 :     if (osMappingTableName.empty())
   10386             :     {
   10387           3 :         int nIndex = 1;
   10388           3 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10389           3 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10390             :         {
   10391           0 :             nIndex += 1;
   10392             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10393           0 :                                       osRightTableName.c_str(), nIndex);
   10394             :         }
   10395             : 
   10396             :         // determine whether base/related keys are unique
   10397           3 :         bool bBaseKeyIsUnique = false;
   10398             :         {
   10399             :             const std::set<std::string> uniqueBaseFieldsUC =
   10400             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10401           6 :                                                osLeftTableName.c_str());
   10402           6 :             if (uniqueBaseFieldsUC.find(
   10403           3 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10404           6 :                 uniqueBaseFieldsUC.end())
   10405             :             {
   10406           2 :                 bBaseKeyIsUnique = true;
   10407             :             }
   10408             :         }
   10409           3 :         bool bRelatedKeyIsUnique = false;
   10410             :         {
   10411             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10412             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10413           6 :                                                osRightTableName.c_str());
   10414           6 :             if (uniqueRelatedFieldsUC.find(
   10415           3 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10416           6 :                 uniqueRelatedFieldsUC.end())
   10417             :             {
   10418           2 :                 bRelatedKeyIsUnique = true;
   10419             :             }
   10420             :         }
   10421             : 
   10422             :         // create mapping table
   10423             : 
   10424           3 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10425           3 :         if (bBaseKeyIsUnique)
   10426             :         {
   10427           2 :             char *pszSQL = sqlite3_mprintf(
   10428             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10429             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10430             :                 "DEFERRED",
   10431             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10432           2 :                 aosLeftTableFields[0].c_str());
   10433           2 :             osBaseIdDefinition += pszSQL;
   10434           2 :             sqlite3_free(pszSQL);
   10435             :         }
   10436             : 
   10437           3 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10438           3 :         if (bRelatedKeyIsUnique)
   10439             :         {
   10440           2 :             char *pszSQL = sqlite3_mprintf(
   10441             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10442             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10443             :                 "DEFERRED",
   10444             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10445           2 :                 aosRightTableFields[0].c_str());
   10446           2 :             osRelatedIdDefinition += pszSQL;
   10447           2 :             sqlite3_free(pszSQL);
   10448             :         }
   10449             : 
   10450           3 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10451             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10452             :                                        "%s, %s);",
   10453             :                                        osMappingTableName.c_str(),
   10454             :                                        osBaseIdDefinition.c_str(),
   10455             :                                        osRelatedIdDefinition.c_str());
   10456           3 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10457           3 :         sqlite3_free(pszSQL);
   10458           3 :         if (eErr != OGRERR_NONE)
   10459             :         {
   10460             :             failureReason =
   10461           0 :                 ("Could not create mapping table " + osMappingTableName)
   10462           0 :                     .c_str();
   10463           0 :             return false;
   10464             :         }
   10465             : 
   10466             :         /*
   10467             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10468             :          * The related tables extension explicitly states that the mapping table should only be
   10469             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10470             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10471             :          *
   10472             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10473             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10474             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10475             :          *
   10476             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10477             :          */
   10478           3 :         pszSQL = sqlite3_mprintf(
   10479             :             "INSERT INTO gpkg_contents "
   10480             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10481             :             "VALUES "
   10482             :             "('%q','attributes','%q','Mapping table for relationship between "
   10483             :             "%q and %q',%s,0)",
   10484             :             osMappingTableName.c_str(), /*table_name*/
   10485             :             osMappingTableName.c_str(), /*identifier*/
   10486             :             osLeftTableName.c_str(),    /*description left table name*/
   10487             :             osRightTableName.c_str(),   /*description right table name*/
   10488           6 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10489             : 
   10490             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10491             :         // supposed to be adding this table to gpkg_contents anyway!
   10492           3 :         (void)SQLCommand(hDB, pszSQL);
   10493           3 :         sqlite3_free(pszSQL);
   10494             : 
   10495           3 :         pszSQL = sqlite3_mprintf(
   10496             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10497             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10498           3 :         eErr = SQLCommand(hDB, pszSQL);
   10499           3 :         sqlite3_free(pszSQL);
   10500           3 :         if (eErr != OGRERR_NONE)
   10501             :         {
   10502           0 :             failureReason = ("Could not create index for " +
   10503           0 :                              osMappingTableName + " (base_id)")
   10504           0 :                                 .c_str();
   10505           0 :             return false;
   10506             :         }
   10507             : 
   10508           3 :         pszSQL = sqlite3_mprintf(
   10509             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10510             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10511           3 :         eErr = SQLCommand(hDB, pszSQL);
   10512           3 :         sqlite3_free(pszSQL);
   10513           3 :         if (eErr != OGRERR_NONE)
   10514             :         {
   10515           0 :             failureReason = ("Could not create index for " +
   10516           0 :                              osMappingTableName + " (related_id)")
   10517           0 :                                 .c_str();
   10518           0 :             return false;
   10519             :         }
   10520             :     }
   10521             :     else
   10522             :     {
   10523             :         // validate mapping table structure
   10524           4 :         if (OGRGeoPackageTableLayer *poLayer =
   10525           4 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10526           4 :                     GetLayerByName(osMappingTableName)))
   10527             :         {
   10528           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10529             :             {
   10530             :                 failureReason =
   10531           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10532           1 :                         .c_str();
   10533           1 :                 return false;
   10534             :             }
   10535           2 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10536             :             {
   10537             :                 failureReason =
   10538           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10539           1 :                         .c_str();
   10540           1 :                 return false;
   10541             :             }
   10542             :         }
   10543             :         else
   10544             :         {
   10545             :             failureReason =
   10546           1 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10547           1 :             return false;
   10548             :         }
   10549             :     }
   10550             : 
   10551           4 :     char *pszSQL = sqlite3_mprintf(
   10552             :         "INSERT INTO gpkg_extensions "
   10553             :         "(table_name,column_name,extension_name,definition,scope) "
   10554             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10555             :         "'http://www.geopackage.org/18-000.html', "
   10556             :         "'read-write')",
   10557             :         osMappingTableName.c_str());
   10558           4 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10559           4 :     sqlite3_free(pszSQL);
   10560           4 :     if (eErr != OGRERR_NONE)
   10561             :     {
   10562           0 :         failureReason = ("Could not insert mapping table " +
   10563           0 :                          osMappingTableName + " into gpkg_extensions")
   10564           0 :                             .c_str();
   10565           0 :         return false;
   10566             :     }
   10567             : 
   10568          12 :     pszSQL = sqlite3_mprintf(
   10569             :         "INSERT INTO gpkgext_relations "
   10570             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10571             :         "primary_column,relation_name,mapping_table_name) "
   10572             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10573           4 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10574           4 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10575             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10576           4 :     eErr = SQLCommand(hDB, pszSQL);
   10577           4 :     sqlite3_free(pszSQL);
   10578           4 :     if (eErr != OGRERR_NONE)
   10579             :     {
   10580           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10581           0 :         return false;
   10582             :     }
   10583             : 
   10584           4 :     ClearCachedRelationships();
   10585           4 :     LoadRelationships();
   10586           4 :     return true;
   10587             : }
   10588             : 
   10589             : /************************************************************************/
   10590             : /*                         DeleteRelationship()                         */
   10591             : /************************************************************************/
   10592             : 
   10593           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10594             :                                                std::string &failureReason)
   10595             : {
   10596           4 :     if (eAccess != GA_Update)
   10597             :     {
   10598           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10599             :                  "DeleteRelationship() not supported on read-only dataset");
   10600           0 :         return false;
   10601             :     }
   10602             : 
   10603             :     // ensure relationships are up to date before we try to remove one
   10604           4 :     ClearCachedRelationships();
   10605           4 :     LoadRelationships();
   10606             : 
   10607           8 :     std::string osMappingTableName;
   10608             :     {
   10609           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10610           4 :         if (poRelationship == nullptr)
   10611             :         {
   10612           1 :             failureReason = "Could not find relationship with name " + name;
   10613           1 :             return false;
   10614             :         }
   10615             : 
   10616           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10617             :     }
   10618             : 
   10619             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10620             :     // refer to poRelationship or any of its members previously obtained here
   10621           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10622             :     {
   10623             :         failureReason =
   10624           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10625             : 
   10626             :         // relationships may have been left in an inconsistent state -- reload
   10627             :         // them now
   10628           0 :         ClearCachedRelationships();
   10629           0 :         LoadRelationships();
   10630           0 :         return false;
   10631             :     }
   10632             : 
   10633           3 :     ClearCachedRelationships();
   10634           3 :     LoadRelationships();
   10635           3 :     return true;
   10636             : }
   10637             : 
   10638             : /************************************************************************/
   10639             : /*                        UpdateRelationship()                          */
   10640             : /************************************************************************/
   10641             : 
   10642           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10643             :     std::unique_ptr<GDALRelationship> &&relationship,
   10644             :     std::string &failureReason)
   10645             : {
   10646           6 :     if (eAccess != GA_Update)
   10647             :     {
   10648           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10649             :                  "UpdateRelationship() not supported on read-only dataset");
   10650           0 :         return false;
   10651             :     }
   10652             : 
   10653             :     // ensure relationships are up to date before we try to update one
   10654           6 :     ClearCachedRelationships();
   10655           6 :     LoadRelationships();
   10656             : 
   10657           6 :     const std::string &osRelationshipName = relationship->GetName();
   10658           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10659           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10660           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10661           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10662           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10663             : 
   10664             :     // sanity checks
   10665             :     {
   10666             :         const GDALRelationship *poExistingRelationship =
   10667           6 :             GetRelationship(osRelationshipName);
   10668           6 :         if (poExistingRelationship == nullptr)
   10669             :         {
   10670             :             failureReason =
   10671           1 :                 "The relationship should already exist to be updated";
   10672           1 :             return false;
   10673             :         }
   10674             : 
   10675           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10676             :         {
   10677           2 :             return false;
   10678             :         }
   10679             : 
   10680             :         // we don't permit changes to the participating tables
   10681           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10682             :         {
   10683           0 :             failureReason = ("Cannot change base table from " +
   10684           0 :                              poExistingRelationship->GetLeftTableName() +
   10685           0 :                              " to " + osLeftTableName)
   10686           0 :                                 .c_str();
   10687           0 :             return false;
   10688             :         }
   10689           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10690             :         {
   10691           0 :             failureReason = ("Cannot change related table from " +
   10692           0 :                              poExistingRelationship->GetRightTableName() +
   10693           0 :                              " to " + osRightTableName)
   10694           0 :                                 .c_str();
   10695           0 :             return false;
   10696             :         }
   10697           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10698             :         {
   10699           0 :             failureReason = ("Cannot change mapping table from " +
   10700           0 :                              poExistingRelationship->GetMappingTableName() +
   10701           0 :                              " to " + osMappingTableName)
   10702           0 :                                 .c_str();
   10703           0 :             return false;
   10704             :         }
   10705             :     }
   10706             : 
   10707           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10708           3 :     if (osRelatedTableType.empty())
   10709             :     {
   10710           0 :         osRelatedTableType = "features";
   10711             :     }
   10712             : 
   10713           3 :     char *pszSQL = sqlite3_mprintf(
   10714             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10715             :         osMappingTableName.c_str());
   10716           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10717           3 :     sqlite3_free(pszSQL);
   10718           3 :     if (eErr != OGRERR_NONE)
   10719             :     {
   10720             :         failureReason =
   10721           0 :             "Could not delete old relationship from gpkgext_relations";
   10722           0 :         return false;
   10723             :     }
   10724             : 
   10725           9 :     pszSQL = sqlite3_mprintf(
   10726             :         "INSERT INTO gpkgext_relations "
   10727             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10728             :         "primary_column,relation_name,mapping_table_name) "
   10729             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10730           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10731           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10732             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10733           3 :     eErr = SQLCommand(hDB, pszSQL);
   10734           3 :     sqlite3_free(pszSQL);
   10735           3 :     if (eErr != OGRERR_NONE)
   10736             :     {
   10737             :         failureReason =
   10738           0 :             "Could not insert updated relationship into gpkgext_relations";
   10739           0 :         return false;
   10740             :     }
   10741             : 
   10742           3 :     ClearCachedRelationships();
   10743           3 :     LoadRelationships();
   10744           3 :     return true;
   10745             : }
   10746             : 
   10747             : /************************************************************************/
   10748             : /*                    GetSqliteMasterContent()                          */
   10749             : /************************************************************************/
   10750             : 
   10751             : const std::vector<SQLSqliteMasterContent> &
   10752           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10753             : {
   10754           2 :     if (m_aoSqliteMasterContent.empty())
   10755             :     {
   10756             :         auto oResultTable =
   10757           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10758           1 :         if (oResultTable)
   10759             :         {
   10760          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10761             :             {
   10762         114 :                 SQLSqliteMasterContent row;
   10763          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10764          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10765          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10766          57 :                 row.osType = pszType ? pszType : "";
   10767          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10768          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10769          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10770             :             }
   10771             :         }
   10772             :     }
   10773           2 :     return m_aoSqliteMasterContent;
   10774             : }

Generated by: LCOV version 1.14