LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gpkg - ogrgeopackagedatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 4005 4470 89.6 %
Date: 2024-05-02 00:41:30 Functions: 132 132 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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      12             :  * copy of this software and associated documentation files (the "Software"),
      13             :  * to deal in the Software without restriction, including without limitation
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15             :  * and/or sell copies of the Software, and to permit persons to whom the
      16             :  * Software is furnished to do so, subject to the following conditions:
      17             :  *
      18             :  * The above copyright notice and this permission notice shall be included
      19             :  * in all copies or substantial portions of the Software.
      20             :  *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27             :  * DEALINGS IN THE SOFTWARE.
      28             :  ****************************************************************************/
      29             : 
      30             : #include "ogr_geopackage.h"
      31             : #include "ogr_p.h"
      32             : #include "ogr_swq.h"
      33             : #include "gdalwarper.h"
      34             : #include "gdal_utils.h"
      35             : #include "ogrgeopackageutility.h"
      36             : #include "ogrsqliteutility.h"
      37             : #include "ogr_wkb.h"
      38             : #include "vrt/vrtdataset.h"
      39             : 
      40             : #include "tilematrixset.hpp"
      41             : 
      42             : #include <cstdlib>
      43             : 
      44             : #include <algorithm>
      45             : #include <limits>
      46             : #include <sstream>
      47             : 
      48             : #define COMPILATION_ALLOWED
      49             : #define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
      50             : #include "ogrsqlitesqlfunctionscommon.cpp"
      51             : 
      52             : // Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
      53             : // ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
      54             : void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
      55             :                                       const GByte *pabyHeader,
      56             :                                       int nHeaderBytes);
      57             : void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
      58             : 
      59             : /************************************************************************/
      60             : /*                             Tiling schemes                           */
      61             : /************************************************************************/
      62             : 
      63             : typedef struct
      64             : {
      65             :     const char *pszName;
      66             :     int nEPSGCode;
      67             :     double dfMinX;
      68             :     double dfMaxY;
      69             :     int nTileXCountZoomLevel0;
      70             :     int nTileYCountZoomLevel0;
      71             :     int nTileWidth;
      72             :     int nTileHeight;
      73             :     double dfPixelXSizeZoomLevel0;
      74             :     double dfPixelYSizeZoomLevel0;
      75             : } TilingSchemeDefinition;
      76             : 
      77             : static const TilingSchemeDefinition asTilingSchemes[] = {
      78             :     /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
      79             :        Annex E.3 */
      80             :     {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
      81             :      360.0 / 256},
      82             : 
      83             :     /* See global-mercator at
      84             :        http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
      85             :     {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
      86             :      256, 78271.516, 78271.516},
      87             : };
      88             : 
      89             : // Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
      90             : constexpr int MAX_ZOOM_LEVEL = 30;
      91             : 
      92             : /************************************************************************/
      93             : /*                     GetTilingScheme()                                */
      94             : /************************************************************************/
      95             : 
      96             : static std::unique_ptr<TilingSchemeDefinition>
      97         500 : GetTilingScheme(const char *pszName)
      98             : {
      99         500 :     if (EQUAL(pszName, "CUSTOM"))
     100         372 :         return nullptr;
     101             : 
     102         256 :     for (const auto &tilingScheme : asTilingSchemes)
     103             :     {
     104         195 :         if (EQUAL(pszName, tilingScheme.pszName))
     105             :         {
     106             :             return std::unique_ptr<TilingSchemeDefinition>(
     107          67 :                 new TilingSchemeDefinition(tilingScheme));
     108             :         }
     109             :     }
     110             : 
     111          61 :     if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
     112           6 :         pszName = "InspireCRS84Quad";
     113             : 
     114         122 :     auto poTM = gdal::TileMatrixSet::parse(pszName);
     115          61 :     if (poTM == nullptr)
     116           1 :         return nullptr;
     117          60 :     if (!poTM->haveAllLevelsSameTopLeft())
     118             :     {
     119           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     120             :                  "Unsupported tiling scheme: not all zoom levels have same top "
     121             :                  "left corner");
     122           0 :         return nullptr;
     123             :     }
     124          60 :     if (!poTM->haveAllLevelsSameTileSize())
     125             :     {
     126           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     127             :                  "Unsupported tiling scheme: not all zoom levels have same "
     128             :                  "tile size");
     129           0 :         return nullptr;
     130             :     }
     131          60 :     if (!poTM->hasOnlyPowerOfTwoVaryingScales())
     132             :     {
     133           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     134             :                  "Unsupported tiling scheme: resolution of consecutive zoom "
     135             :                  "levels is not always 2");
     136           1 :         return nullptr;
     137             :     }
     138          59 :     if (poTM->hasVariableMatrixWidth())
     139             :     {
     140           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     141             :                  "Unsupported tiling scheme: some levels have variable matrix "
     142             :                  "width");
     143           0 :         return nullptr;
     144             :     }
     145         118 :     auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
     146          59 :     poTilingScheme->pszName = pszName;
     147             : 
     148         118 :     OGRSpatialReference oSRS;
     149          59 :     if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
     150             :     {
     151           0 :         return nullptr;
     152             :     }
     153          59 :     if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
     154             :     {
     155           6 :         poTilingScheme->nEPSGCode = 4326;
     156             :     }
     157             :     else
     158             :     {
     159          53 :         const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
     160          53 :         const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
     161          53 :         if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
     162             :             pszAuthCode == nullptr)
     163             :         {
     164           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     165             :                      "Unsupported tiling scheme: only EPSG CRS supported");
     166           0 :             return nullptr;
     167             :         }
     168          53 :         poTilingScheme->nEPSGCode = atoi(pszAuthCode);
     169             :     }
     170          59 :     const auto &zoomLevel0 = poTM->tileMatrixList()[0];
     171          59 :     poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
     172          59 :     poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
     173          59 :     poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
     174          59 :     poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
     175          59 :     poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
     176          59 :     poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
     177          59 :     poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
     178          59 :     poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
     179             : 
     180         118 :     const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
     181          59 :                              oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
     182          59 :     if (bInvertAxis)
     183             :     {
     184           6 :         std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
     185           6 :         std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
     186           6 :                   poTilingScheme->dfPixelYSizeZoomLevel0);
     187             :     }
     188          59 :     return poTilingScheme;
     189             : }
     190             : 
     191             : static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
     192             :     "CREATE TABLE gpkg_geometry_columns ("
     193             :     "table_name TEXT NOT NULL,"
     194             :     "column_name TEXT NOT NULL,"
     195             :     "geometry_type_name TEXT NOT NULL,"
     196             :     "srs_id INTEGER NOT NULL,"
     197             :     "z TINYINT NOT NULL,"
     198             :     "m TINYINT NOT NULL,"
     199             :     "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
     200             :     "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
     201             :     "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
     202             :     "gpkg_contents(table_name),"
     203             :     "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
     204             :     "(srs_id)"
     205             :     ")";
     206             : 
     207             : /* Only recent versions of SQLite will let us muck with application_id */
     208             : /* via a PRAGMA statement, so we have to write directly into the */
     209             : /* file header here. */
     210             : /* We do this at the *end* of initialization so that there is */
     211             : /* data to write down to a file, and we will have a writable file */
     212             : /* once we close the SQLite connection */
     213         708 : OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
     214             : {
     215         708 :     CPLAssert(hDB != nullptr);
     216             : 
     217         708 :     const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
     218             :                                                 "PRAGMA user_version = %u",
     219             :                                                 m_nApplicationId,
     220        1416 :                                                 m_nUserVersion));
     221        1416 :     return SQLCommand(hDB, osPragma.c_str());
     222             : }
     223             : 
     224        2029 : bool GDALGeoPackageDataset::CloseDB()
     225             : {
     226        2029 :     OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
     227        2029 :     m_pSQLFunctionData = nullptr;
     228        2029 :     return OGRSQLiteBaseDataSource::CloseDB();
     229             : }
     230             : 
     231          11 : bool GDALGeoPackageDataset::ReOpenDB()
     232             : {
     233          11 :     CPLAssert(hDB != nullptr);
     234          11 :     CPLAssert(m_pszFilename != nullptr);
     235             : 
     236          11 :     FinishSpatialite();
     237             : 
     238          11 :     CloseDB();
     239             : 
     240             :     /* And re-open the file */
     241          11 :     return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
     242             : }
     243             : 
     244         647 : static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
     245             :                                      int nEPSGCode)
     246             : {
     247         647 :     CPLPushErrorHandler(CPLQuietErrorHandler);
     248         647 :     const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
     249         647 :     CPLPopErrorHandler();
     250         647 :     CPLErrorReset();
     251         647 :     return eErr;
     252             : }
     253             : 
     254             : OGRSpatialReference *
     255         963 : GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
     256             :                                      bool bEmitErrorIfNotFound)
     257             : {
     258             :     std::map<int, OGRSpatialReference *>::const_iterator oIter =
     259         963 :         m_oMapSrsIdToSrs.find(iSrsId);
     260         963 :     if (oIter != m_oMapSrsIdToSrs.end())
     261             :     {
     262          64 :         if (oIter->second == nullptr)
     263          29 :             return nullptr;
     264          35 :         oIter->second->Reference();
     265          35 :         return oIter->second;
     266             :     }
     267             : 
     268         899 :     if (iSrsId == 0 || iSrsId == -1)
     269             :     {
     270         117 :         OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     271         117 :         poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     272             : 
     273             :         // See corresponding tests in GDALGeoPackageDataset::GetSrsId
     274         117 :         if (iSrsId == 0)
     275             :         {
     276          28 :             poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
     277             :                                     "unknown", SRS_WGS84_SEMIMAJOR,
     278             :                                     SRS_WGS84_INVFLATTENING);
     279             :         }
     280          89 :         else if (iSrsId == -1)
     281             :         {
     282          89 :             poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
     283          89 :             poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
     284             :         }
     285             : 
     286         117 :         m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     287         117 :         poSpatialRef->Reference();
     288         117 :         return poSpatialRef;
     289             :     }
     290             : 
     291        1564 :     CPLString oSQL;
     292         782 :     oSQL.Printf("SELECT srs_name, definition, organization, "
     293             :                 "organization_coordsys_id%s%s "
     294             :                 "FROM gpkg_spatial_ref_sys WHERE "
     295             :                 "srs_id = %d LIMIT 2",
     296         782 :                 m_bHasDefinition12_063 ? ", definition_12_063" : "",
     297         782 :                 m_bHasEpochColumn ? ", epoch" : "", iSrsId);
     298             : 
     299        1564 :     auto oResult = SQLQuery(hDB, oSQL.c_str());
     300             : 
     301         782 :     if (!oResult || oResult->RowCount() != 1)
     302             :     {
     303          11 :         if (bFallbackToEPSG)
     304             :         {
     305           6 :             CPLDebug("GPKG",
     306             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     307             :                      iSrsId);
     308           6 :             OGRSpatialReference *poSRS = new OGRSpatialReference();
     309           6 :             if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
     310             :             {
     311           5 :                 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     312           5 :                 return poSRS;
     313             :             }
     314           1 :             poSRS->Release();
     315             :         }
     316           5 :         else if (bEmitErrorIfNotFound)
     317             :         {
     318           2 :             CPLError(CE_Warning, CPLE_AppDefined,
     319             :                      "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
     320             :                      iSrsId);
     321           2 :             m_oMapSrsIdToSrs[iSrsId] = nullptr;
     322             :         }
     323           6 :         return nullptr;
     324             :     }
     325             : 
     326         771 :     const char *pszName = oResult->GetValue(0, 0);
     327         771 :     if (pszName && EQUAL(pszName, "Undefined SRS"))
     328             :     {
     329         295 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     330         295 :         return nullptr;
     331             :     }
     332         476 :     const char *pszWkt = oResult->GetValue(1, 0);
     333         476 :     if (pszWkt == nullptr)
     334           0 :         return nullptr;
     335         476 :     const char *pszOrganization = oResult->GetValue(2, 0);
     336         476 :     const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
     337             :     const char *pszWkt2 =
     338         476 :         m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
     339         476 :     if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
     340          71 :         pszWkt = pszWkt2;
     341             :     const char *pszCoordinateEpoch =
     342         476 :         m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
     343             :     const double dfCoordinateEpoch =
     344         476 :         pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
     345             : 
     346         476 :     OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
     347         476 :     poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     348             :     // Try to import first from EPSG code, and then from WKT
     349         476 :     if (!(pszOrganization && pszOrganizationCoordsysID &&
     350         476 :           EQUAL(pszOrganization, "EPSG") &&
     351         457 :           (atoi(pszOrganizationCoordsysID) == iSrsId ||
     352           4 :            (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
     353         457 :           GDALGPKGImportFromEPSG(
     354         952 :               poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
     355          19 :         poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
     356             :     {
     357           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     358             :                  "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
     359             :                  pszWkt);
     360           0 :         delete poSpatialRef;
     361           0 :         m_oMapSrsIdToSrs[iSrsId] = nullptr;
     362           0 :         return nullptr;
     363             :     }
     364             : 
     365         476 :     poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
     366         476 :     poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
     367         476 :     m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
     368         476 :     poSpatialRef->Reference();
     369         476 :     return poSpatialRef;
     370             : }
     371             : 
     372         217 : const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
     373             : {
     374         217 :     const char *pszName = oSRS.GetName();
     375         217 :     if (pszName)
     376         217 :         return pszName;
     377             : 
     378             :     // Something odd.  Return empty.
     379           0 :     return "Unnamed SRS";
     380             : }
     381             : 
     382             : /* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
     383           6 : bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
     384             :     bool bForceEpoch)
     385             : {
     386           6 :     const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
     387             :     auto oResultTable = SQLQuery(
     388             :         hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
     389          12 :              "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
     390           6 :     if (!oResultTable)
     391           0 :         return false;
     392             : 
     393             :     // Temporary remove foreign key checks
     394             :     const GPKGTemporaryForeignKeyCheckDisabler
     395           6 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
     396             : 
     397           6 :     bool bRet = SoftStartTransaction() == OGRERR_NONE;
     398             : 
     399           6 :     if (bRet)
     400             :     {
     401             :         std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
     402             :                           "srs_name TEXT NOT NULL,"
     403             :                           "srs_id INTEGER NOT NULL PRIMARY KEY,"
     404             :                           "organization TEXT NOT NULL,"
     405             :                           "organization_coordsys_id INTEGER NOT NULL,"
     406             :                           "definition TEXT NOT NULL,"
     407             :                           "description TEXT, "
     408           6 :                           "definition_12_063 TEXT NOT NULL");
     409           6 :         if (bAddEpoch)
     410           3 :             osSQL += ", epoch DOUBLE";
     411           6 :         osSQL += ")";
     412           6 :         bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
     413             :     }
     414             : 
     415           6 :     if (bRet)
     416             :     {
     417          28 :         for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
     418             :         {
     419          22 :             const char *pszSrsName = oResultTable->GetValue(0, i);
     420          22 :             const char *pszSrsId = oResultTable->GetValue(1, i);
     421          22 :             const char *pszOrganization = oResultTable->GetValue(2, i);
     422             :             const char *pszOrganizationCoordsysID =
     423          22 :                 oResultTable->GetValue(3, i);
     424          22 :             const char *pszDefinition = oResultTable->GetValue(4, i);
     425             :             if (pszSrsName == nullptr || pszSrsId == nullptr ||
     426             :                 pszOrganization == nullptr ||
     427             :                 pszOrganizationCoordsysID == nullptr)
     428             :             {
     429             :                 // should not happen as there are NOT NULL constraints
     430             :                 // But a database could lack such NOT NULL constraints or have
     431             :                 // large values that would cause a memory allocation failure.
     432             :             }
     433          22 :             const char *pszDescription = oResultTable->GetValue(5, i);
     434             :             char *pszSQL;
     435             : 
     436          44 :             OGRSpatialReference oSRS;
     437          22 :             if (pszOrganization && pszOrganizationCoordsysID &&
     438          22 :                 EQUAL(pszOrganization, "EPSG"))
     439             :             {
     440           8 :                 oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
     441             :             }
     442          30 :             if (!oSRS.IsEmpty() && pszDefinition &&
     443           8 :                 !EQUAL(pszDefinition, "undefined"))
     444             :             {
     445           8 :                 oSRS.SetFromUserInput(pszDefinition);
     446             :             }
     447          22 :             char *pszWKT2 = nullptr;
     448          22 :             if (!oSRS.IsEmpty())
     449             :             {
     450           8 :                 const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
     451             :                                                        nullptr};
     452           8 :                 oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
     453           8 :                 if (pszWKT2 && pszWKT2[0] == '\0')
     454             :                 {
     455           0 :                     CPLFree(pszWKT2);
     456           0 :                     pszWKT2 = nullptr;
     457             :                 }
     458             :             }
     459          22 :             if (pszWKT2 == nullptr)
     460             :             {
     461          14 :                 pszWKT2 = CPLStrdup("undefined");
     462             :             }
     463             : 
     464          22 :             if (pszDescription)
     465             :             {
     466          19 :                 pszSQL = sqlite3_mprintf(
     467             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     468             :                     "organization, organization_coordsys_id, definition, "
     469             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     470             :                     "'%q', '%q', '%q', '%q')",
     471             :                     pszSrsName, pszSrsId, pszOrganization,
     472             :                     pszOrganizationCoordsysID, pszDefinition, pszDescription,
     473             :                     pszWKT2);
     474             :             }
     475             :             else
     476             :             {
     477           3 :                 pszSQL = sqlite3_mprintf(
     478             :                     "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
     479             :                     "organization, organization_coordsys_id, definition, "
     480             :                     "description, definition_12_063) VALUES ('%q', '%q', '%q', "
     481             :                     "'%q', '%q', NULL, '%q')",
     482             :                     pszSrsName, pszSrsId, pszOrganization,
     483             :                     pszOrganizationCoordsysID, pszDefinition, pszWKT2);
     484             :             }
     485             : 
     486          22 :             CPLFree(pszWKT2);
     487          22 :             bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
     488          22 :             sqlite3_free(pszSQL);
     489             :         }
     490             :     }
     491             : 
     492           6 :     if (bRet)
     493             :     {
     494           6 :         bRet =
     495           6 :             SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
     496             :     }
     497           6 :     if (bRet)
     498             :     {
     499           6 :         bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
     500             :                                "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
     501             :     }
     502           6 :     if (bRet)
     503             :     {
     504          12 :         bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
     505           6 :                OGRERR_NONE == SQLCommand(hDB,
     506             :                                          "INSERT INTO gpkg_extensions "
     507             :                                          "(table_name, column_name, "
     508             :                                          "extension_name, definition, scope) "
     509             :                                          "VALUES "
     510             :                                          "('gpkg_spatial_ref_sys', "
     511             :                                          "'definition_12_063', 'gpkg_crs_wkt', "
     512             :                                          "'http://www.geopackage.org/spec120/"
     513             :                                          "#extension_crs_wkt', 'read-write')");
     514             :     }
     515           6 :     if (bRet && bAddEpoch)
     516             :     {
     517           3 :         bRet =
     518             :             OGRERR_NONE ==
     519           3 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     520             :                                 "'gpkg_crs_wkt_1_1' "
     521           6 :                                 "WHERE extension_name = 'gpkg_crs_wkt'") &&
     522             :             OGRERR_NONE ==
     523           3 :                 SQLCommand(
     524             :                     hDB,
     525             :                     "INSERT INTO gpkg_extensions "
     526             :                     "(table_name, column_name, extension_name, definition, "
     527             :                     "scope) "
     528             :                     "VALUES "
     529             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     530             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     531             :                     "'read-write')");
     532             :     }
     533           6 :     if (bRet)
     534             :     {
     535           6 :         SoftCommitTransaction();
     536           6 :         m_bHasDefinition12_063 = true;
     537           6 :         if (bAddEpoch)
     538           3 :             m_bHasEpochColumn = true;
     539             :     }
     540             :     else
     541             :     {
     542           0 :         SoftRollbackTransaction();
     543             :     }
     544             : 
     545           6 :     return bRet;
     546             : }
     547             : 
     548         703 : int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
     549             : {
     550         703 :     const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
     551        1020 :     if (!poSRSIn || poSRSIn->IsEmpty() ||
     552         317 :         (pszName && EQUAL(pszName, "Undefined SRS")))
     553             :     {
     554         388 :         OGRErr err = OGRERR_NONE;
     555         388 :         const int nSRSId = SQLGetInteger(
     556             :             hDB,
     557             :             "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
     558             :             "'Undefined SRS' AND organization = 'GDAL'",
     559             :             &err);
     560         388 :         if (err == OGRERR_NONE)
     561          52 :             return nSRSId;
     562             : 
     563             :         // The below WKT definitions are somehow questionable (using a unknown
     564             :         // unit). For GDAL >= 3.9, they won't be used. They will only be used
     565             :         // for earlier versions.
     566             :         const char *pszSQL;
     567             : #define UNDEFINED_CRS_SRS_ID 99999
     568             :         static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
     569             : #define STRINGIFY(x) #x
     570             : #define XSTRINGIFY(x) STRINGIFY(x)
     571         336 :         if (m_bHasDefinition12_063)
     572             :         {
     573             :             /* clang-format off */
     574           1 :             pszSQL =
     575             :                 "INSERT INTO gpkg_spatial_ref_sys "
     576             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     577             :                 "definition, definition_12_063, description) VALUES "
     578             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     579             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     580             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     581             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     582             :                 "AXIS[\"Northing\",NORTH]]',"
     583             :                 "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
     584             :                 "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
     585             :                 "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
     586             :                 "'Custom undefined coordinate reference system')";
     587             :             /* clang-format on */
     588             :         }
     589             :         else
     590             :         {
     591             :             /* clang-format off */
     592         335 :             pszSQL =
     593             :                 "INSERT INTO gpkg_spatial_ref_sys "
     594             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     595             :                 "definition, description) VALUES "
     596             :                 "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
     597             :                 XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
     598             :                 "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
     599             :                 "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
     600             :                 "AXIS[\"Northing\",NORTH]]',"
     601             :                 "'Custom undefined coordinate reference system')";
     602             :             /* clang-format on */
     603             :         }
     604         336 :         if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
     605         336 :             return UNDEFINED_CRS_SRS_ID;
     606             : #undef UNDEFINED_CRS_SRS_ID
     607             : #undef XSTRINGIFY
     608             : #undef STRINGIFY
     609           0 :         return -1;
     610             :     }
     611             : 
     612         630 :     std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
     613             : 
     614         315 :     if (poSRS->IsGeographic() || poSRS->IsLocal())
     615             :     {
     616             :         // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
     617         120 :         if (pszName != nullptr && strlen(pszName) > 0)
     618             :         {
     619         120 :             if (EQUAL(pszName, "Undefined geographic SRS"))
     620           2 :                 return 0;
     621             : 
     622         118 :             if (EQUAL(pszName, "Undefined Cartesian SRS"))
     623           1 :                 return -1;
     624             :         }
     625             :     }
     626             : 
     627         312 :     const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     628             : 
     629         312 :     if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
     630             :     {
     631             :         // Try to force identify an EPSG code.
     632          24 :         poSRS->AutoIdentifyEPSG();
     633             : 
     634          24 :         pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     635          24 :         if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
     636             :         {
     637           0 :             const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     638           0 :             if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
     639             :             {
     640             :                 /* Import 'clean' SRS */
     641           0 :                 poSRS->importFromEPSG(atoi(pszAuthorityCode));
     642             : 
     643           0 :                 pszAuthorityName = poSRS->GetAuthorityName(nullptr);
     644             :             }
     645             :         }
     646             : 
     647          24 :         poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
     648             :     }
     649             : 
     650             :     // Check whether the EPSG authority code is already mapped to a
     651             :     // SRS ID.
     652         312 :     char *pszSQL = nullptr;
     653         312 :     int nSRSId = DEFAULT_SRID;
     654         312 :     int nAuthorityCode = 0;
     655         312 :     OGRErr err = OGRERR_NONE;
     656         312 :     bool bCanUseAuthorityCode = false;
     657         312 :     const char *const apszIsSameOptions[] = {
     658             :         "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
     659             :         "IGNORE_COORDINATE_EPOCH=YES", nullptr};
     660         312 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
     661             :     {
     662         288 :         const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
     663         288 :         if (pszAuthorityCode)
     664             :         {
     665         288 :             if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
     666             :             {
     667         288 :                 nAuthorityCode = atoi(pszAuthorityCode);
     668             :             }
     669             :             else
     670             :             {
     671           0 :                 CPLDebug("GPKG",
     672             :                          "SRS has %s:%s identification, but the code not "
     673             :                          "being an integer value cannot be stored as such "
     674             :                          "in the database.",
     675             :                          pszAuthorityName, pszAuthorityCode);
     676           0 :                 pszAuthorityName = nullptr;
     677             :             }
     678             :         }
     679             :     }
     680             : 
     681         600 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     682         288 :         poSRSIn->GetCoordinateEpoch() == 0)
     683             :     {
     684             :         pszSQL =
     685         283 :             sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     686             :                             "upper(organization) = upper('%q') AND "
     687             :                             "organization_coordsys_id = %d",
     688             :                             pszAuthorityName, nAuthorityCode);
     689             : 
     690         283 :         nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     691         283 :         sqlite3_free(pszSQL);
     692             : 
     693             :         // Got a match? Return it!
     694         283 :         if (OGRERR_NONE == err)
     695             :         {
     696          91 :             auto poRefSRS = GetSpatialRef(nSRSId);
     697             :             bool bOK =
     698          91 :                 (poRefSRS == nullptr ||
     699          92 :                  poSRS->IsSame(poRefSRS, apszIsSameOptions) ||
     700           1 :                  !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
     701          91 :             if (poRefSRS)
     702          91 :                 poRefSRS->Release();
     703          91 :             if (bOK)
     704             :             {
     705          90 :                 return nSRSId;
     706             :             }
     707             :             else
     708             :             {
     709           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     710             :                          "Passed SRS uses %s:%d identification, but its "
     711             :                          "definition is not compatible with the "
     712             :                          "definition of that object already in the database. "
     713             :                          "Registering it as a new entry into the database.",
     714             :                          pszAuthorityName, nAuthorityCode);
     715           1 :                 pszAuthorityName = nullptr;
     716           1 :                 nAuthorityCode = 0;
     717             :             }
     718             :         }
     719             :     }
     720             : 
     721             :     // Translate SRS to WKT.
     722         222 :     CPLCharUniquePtr pszWKT1;
     723         222 :     CPLCharUniquePtr pszWKT2_2015;
     724         222 :     CPLCharUniquePtr pszWKT2_2019;
     725         222 :     const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
     726         222 :     const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
     727         222 :     const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
     728             : 
     729         444 :     std::string osEpochTest;
     730         222 :     if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
     731             :     {
     732             :         osEpochTest =
     733           3 :             CPLSPrintf(" AND epoch = %.18g", poSRSIn->GetCoordinateEpoch());
     734             :     }
     735             : 
     736         222 :     if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
     737             :     {
     738         215 :         char *pszTmp = nullptr;
     739         215 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
     740         215 :         pszWKT1.reset(pszTmp);
     741         215 :         if (pszWKT1 && pszWKT1.get()[0] == '\0')
     742             :         {
     743           0 :             pszWKT1.reset();
     744             :         }
     745             :     }
     746             :     {
     747         222 :         char *pszTmp = nullptr;
     748         222 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
     749         222 :         pszWKT2_2015.reset(pszTmp);
     750         222 :         if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
     751             :         {
     752           0 :             pszWKT2_2015.reset();
     753             :         }
     754             :     }
     755             :     {
     756         222 :         char *pszTmp = nullptr;
     757         222 :         poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
     758         222 :         pszWKT2_2019.reset(pszTmp);
     759         222 :         if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
     760             :         {
     761           0 :             pszWKT2_2019.reset();
     762             :         }
     763             :     }
     764             : 
     765         222 :     if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
     766             :     {
     767           0 :         return DEFAULT_SRID;
     768             :     }
     769             : 
     770         222 :     if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
     771             :     {
     772             :         // Search if there is already an existing entry with this WKT
     773         219 :         if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
     774             :         {
     775          39 :             if (pszWKT1)
     776             :             {
     777         136 :                 pszSQL = sqlite3_mprintf(
     778             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     779             :                     "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
     780             :                     pszWKT1.get(),
     781          68 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     782          68 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     783             :                     osEpochTest.c_str());
     784             :             }
     785             :             else
     786             :             {
     787          20 :                 pszSQL = sqlite3_mprintf(
     788             :                     "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     789             :                     "definition_12_063 IN ('%q', '%q')%s",
     790          10 :                     pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
     791          10 :                     pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
     792             :                     osEpochTest.c_str());
     793             :             }
     794             :         }
     795         180 :         else if (pszWKT1)
     796             :         {
     797             :             pszSQL =
     798         178 :                 sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
     799             :                                 "definition = '%q'%s",
     800             :                                 pszWKT1.get(), osEpochTest.c_str());
     801             :         }
     802             :         else
     803             :         {
     804           2 :             pszSQL = nullptr;
     805             :         }
     806         219 :         if (pszSQL)
     807             :         {
     808         217 :             nSRSId = SQLGetInteger(hDB, pszSQL, &err);
     809         217 :             sqlite3_free(pszSQL);
     810         217 :             if (OGRERR_NONE == err)
     811             :             {
     812           5 :                 return nSRSId;
     813             :             }
     814             :         }
     815             :     }
     816             : 
     817         412 :     if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
     818         195 :         poSRSIn->GetCoordinateEpoch() == 0)
     819             :     {
     820         191 :         bool bTryToReuseSRSId = true;
     821         191 :         if (EQUAL(pszAuthorityName, "EPSG"))
     822             :         {
     823         380 :             OGRSpatialReference oSRS_EPSG;
     824         190 :             if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
     825             :                 OGRERR_NONE)
     826             :             {
     827         191 :                 if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
     828           1 :                     CPLTestBool(
     829             :                         CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
     830             :                 {
     831           1 :                     bTryToReuseSRSId = false;
     832           1 :                     CPLError(
     833             :                         CE_Warning, CPLE_AppDefined,
     834             :                         "Passed SRS uses %s:%d identification, but its "
     835             :                         "definition is not compatible with the "
     836             :                         "official definition of the object. "
     837             :                         "Registering it as a non-%s entry into the database.",
     838             :                         pszAuthorityName, nAuthorityCode, pszAuthorityName);
     839           1 :                     pszAuthorityName = nullptr;
     840           1 :                     nAuthorityCode = 0;
     841             :                 }
     842             :             }
     843             :         }
     844         191 :         if (bTryToReuseSRSId)
     845             :         {
     846             :             // No match, but maybe we can use the nAuthorityCode as the nSRSId?
     847         190 :             pszSQL = sqlite3_mprintf(
     848             :                 "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
     849             :                 "srs_id = %d",
     850             :                 nAuthorityCode);
     851             : 
     852             :             // Yep, we can!
     853         190 :             if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
     854         189 :                 bCanUseAuthorityCode = true;
     855         190 :             sqlite3_free(pszSQL);
     856             :         }
     857             :     }
     858             : 
     859         217 :     bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
     860         217 :     bool bForceEpoch = false;
     861         219 :     if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
     862           2 :         (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
     863             :     {
     864           2 :         bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     865             :     }
     866             : 
     867             :     // Add epoch column if needed
     868         217 :     if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
     869             :     {
     870           3 :         if (m_bHasDefinition12_063)
     871             :         {
     872           0 :             if (SoftStartTransaction() != OGRERR_NONE)
     873           0 :                 return DEFAULT_SRID;
     874           0 :             if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
     875           0 :                                 "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
     876           0 :                 SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
     877             :                                 "'gpkg_crs_wkt_1_1' "
     878             :                                 "WHERE extension_name = 'gpkg_crs_wkt'") !=
     879           0 :                     OGRERR_NONE ||
     880           0 :                 SQLCommand(
     881             :                     hDB,
     882             :                     "INSERT INTO gpkg_extensions "
     883             :                     "(table_name, column_name, extension_name, definition, "
     884             :                     "scope) "
     885             :                     "VALUES "
     886             :                     "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
     887             :                     "'http://www.geopackage.org/spec/#extension_crs_wkt', "
     888             :                     "'read-write')") != OGRERR_NONE)
     889             :             {
     890           0 :                 SoftRollbackTransaction();
     891           0 :                 return DEFAULT_SRID;
     892             :             }
     893             : 
     894           0 :             if (SoftCommitTransaction() != OGRERR_NONE)
     895           0 :                 return DEFAULT_SRID;
     896             : 
     897           0 :             m_bHasEpochColumn = true;
     898             :         }
     899             :         else
     900             :         {
     901           3 :             bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
     902           3 :             bForceEpoch = true;
     903             :         }
     904             :     }
     905             : 
     906         222 :     if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
     907           5 :         !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
     908             :     {
     909           0 :         return DEFAULT_SRID;
     910             :     }
     911             : 
     912             :     // Reuse the authority code number as SRS_ID if we can
     913         217 :     if (bCanUseAuthorityCode)
     914             :     {
     915         189 :         nSRSId = nAuthorityCode;
     916             :     }
     917             :     // Otherwise, generate a new SRS_ID number (max + 1)
     918             :     else
     919             :     {
     920             :         // Get the current maximum srid in the srs table.
     921          28 :         const int nMaxSRSId = SQLGetInteger(
     922             :             hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
     923          28 :         nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
     924             :     }
     925             : 
     926         434 :     std::string osEpochColumn;
     927         217 :     std::string osEpochVal;
     928         217 :     if (poSRSIn->GetCoordinateEpoch() > 0)
     929             :     {
     930           5 :         osEpochColumn = ", epoch";
     931           5 :         osEpochVal = CPLSPrintf(", %.18g", poSRSIn->GetCoordinateEpoch());
     932             :     }
     933             : 
     934             :     // Add new SRS row to gpkg_spatial_ref_sys.
     935         217 :     if (m_bHasDefinition12_063)
     936             :     {
     937             :         // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
     938          41 :         const char *pszWKT2 = poSRSIn->IsDynamic() &&
     939           8 :                                       poSRSIn->GetCoordinateEpoch() > 0 &&
     940           1 :                                       pszWKT2_2019
     941           1 :                                   ? pszWKT2_2019.get()
     942          40 :                               : pszWKT2_2015 ? pszWKT2_2015.get()
     943          87 :                                              : pszWKT2_2019.get();
     944             : 
     945          41 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     946             :         {
     947          90 :             pszSQL = sqlite3_mprintf(
     948             :                 "INSERT INTO gpkg_spatial_ref_sys "
     949             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     950             :                 "definition, definition_12_063%s) VALUES "
     951             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     952          30 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
     953             :                 pszAuthorityName, nAuthorityCode,
     954          57 :                 pszWKT1 ? pszWKT1.get() : "undefined",
     955             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     956             :         }
     957             :         else
     958             :         {
     959          33 :             pszSQL = sqlite3_mprintf(
     960             :                 "INSERT INTO gpkg_spatial_ref_sys "
     961             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     962             :                 "definition, definition_12_063%s) VALUES "
     963             :                 "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
     964          11 :                 osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
     965          20 :                 nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
     966             :                 pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
     967             :         }
     968             :     }
     969             :     else
     970             :     {
     971         176 :         if (pszAuthorityName != nullptr && nAuthorityCode > 0)
     972             :         {
     973         328 :             pszSQL = sqlite3_mprintf(
     974             :                 "INSERT INTO gpkg_spatial_ref_sys "
     975             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     976             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     977         164 :                 GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
     978         328 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     979             :         }
     980             :         else
     981             :         {
     982          24 :             pszSQL = sqlite3_mprintf(
     983             :                 "INSERT INTO gpkg_spatial_ref_sys "
     984             :                 "(srs_name,srs_id,organization,organization_coordsys_id,"
     985             :                 "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
     986          12 :                 GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
     987          24 :                 pszWKT1 ? pszWKT1.get() : "undefined");
     988             :         }
     989             :     }
     990             : 
     991             :     // Add new row to gpkg_spatial_ref_sys.
     992         217 :     CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
     993             : 
     994             :     // Free everything that was allocated.
     995         217 :     sqlite3_free(pszSQL);
     996             : 
     997         217 :     return nSRSId;
     998             : }
     999             : 
    1000             : /************************************************************************/
    1001             : /*                        GDALGeoPackageDataset()                       */
    1002             : /************************************************************************/
    1003             : 
    1004        2018 : GDALGeoPackageDataset::GDALGeoPackageDataset()
    1005             :     : m_nApplicationId(GPKG_APPLICATION_ID), m_nUserVersion(GPKG_1_2_VERSION),
    1006             :       m_papoLayers(nullptr), m_nLayers(0),
    1007             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1008             :       m_bHasGPKGOGRContents(false),
    1009             : #endif
    1010             :       m_bHasGPKGGeometryColumns(false), m_bHasDefinition12_063(false),
    1011             :       m_bIdentifierAsCO(false), m_bDescriptionAsCO(false),
    1012             :       m_bHasReadMetadataFromStorage(false), m_bMetadataDirty(false),
    1013             :       m_bRecordInsertedInGPKGContent(false), m_bGeoTransformValid(false),
    1014             :       m_nSRID(-1),  // Unknown Cartesian.
    1015             :       m_dfTMSMinX(0.0), m_dfTMSMaxY(0.0), m_nOverviewCount(0),
    1016             :       m_papoOverviewDS(nullptr), m_bZoomOther(false), m_bInFlushCache(false),
    1017             :       m_osTilingScheme("CUSTOM"), m_bMapTableToExtensionsBuilt(false),
    1018        2018 :       m_bMapTableToContentsBuilt(false)
    1019             : {
    1020        2018 :     m_adfGeoTransform[0] = 0.0;
    1021        2018 :     m_adfGeoTransform[1] = 1.0;
    1022        2018 :     m_adfGeoTransform[2] = 0.0;
    1023        2018 :     m_adfGeoTransform[3] = 0.0;
    1024        2018 :     m_adfGeoTransform[4] = 0.0;
    1025        2018 :     m_adfGeoTransform[5] = 1.0;
    1026        2018 : }
    1027             : 
    1028             : /************************************************************************/
    1029             : /*                       ~GDALGeoPackageDataset()                       */
    1030             : /************************************************************************/
    1031             : 
    1032        4036 : GDALGeoPackageDataset::~GDALGeoPackageDataset()
    1033             : {
    1034        2018 :     GDALGeoPackageDataset::Close();
    1035        4036 : }
    1036             : 
    1037             : /************************************************************************/
    1038             : /*                              Close()                                 */
    1039             : /************************************************************************/
    1040             : 
    1041        3380 : CPLErr GDALGeoPackageDataset::Close()
    1042             : {
    1043        3380 :     CPLErr eErr = CE_None;
    1044        3380 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
    1045             :     {
    1046        1209 :         if (eAccess == GA_Update && m_poParentDS == nullptr &&
    1047        3227 :             !m_osRasterTable.empty() && !m_bGeoTransformValid)
    1048             :         {
    1049           3 :             CPLError(CE_Failure, CPLE_AppDefined,
    1050             :                      "Raster table %s not correctly initialized due to missing "
    1051             :                      "call to SetGeoTransform()",
    1052             :                      m_osRasterTable.c_str());
    1053             :         }
    1054             : 
    1055        2018 :         if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
    1056           7 :             eErr = CE_Failure;
    1057             : 
    1058             :         // Destroy bands now since we don't want
    1059             :         // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
    1060             :         // destruction
    1061        3769 :         for (int i = 0; i < nBands; i++)
    1062        1751 :             delete papoBands[i];
    1063        2018 :         nBands = 0;
    1064        2018 :         CPLFree(papoBands);
    1065        2018 :         papoBands = nullptr;
    1066             : 
    1067             :         // Destroy overviews before cleaning m_hTempDB as they could still
    1068             :         // need it
    1069        2339 :         for (int i = 0; i < m_nOverviewCount; i++)
    1070         321 :             delete m_papoOverviewDS[i];
    1071             : 
    1072        2018 :         if (m_poParentDS)
    1073             :         {
    1074         321 :             hDB = nullptr;
    1075             :         }
    1076             : 
    1077        5417 :         for (int i = 0; i < m_nLayers; i++)
    1078        3399 :             delete m_papoLayers[i];
    1079             : 
    1080        2018 :         CPLFree(m_papoLayers);
    1081        2018 :         CPLFree(m_papoOverviewDS);
    1082             : 
    1083             :         std::map<int, OGRSpatialReference *>::iterator oIter =
    1084        2018 :             m_oMapSrsIdToSrs.begin();
    1085        2908 :         for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
    1086             :         {
    1087         890 :             OGRSpatialReference *poSRS = oIter->second;
    1088         890 :             if (poSRS)
    1089         593 :                 poSRS->Release();
    1090             :         }
    1091             : 
    1092        2018 :         if (!CloseDB())
    1093           0 :             eErr = CE_Failure;
    1094             : 
    1095        2018 :         if (OGRSQLiteBaseDataSource::Close() != CE_None)
    1096           0 :             eErr = CE_Failure;
    1097             :     }
    1098        3380 :     return eErr;
    1099             : }
    1100             : 
    1101             : /************************************************************************/
    1102             : /*                         ICanIWriteBlock()                            */
    1103             : /************************************************************************/
    1104             : 
    1105        5664 : bool GDALGeoPackageDataset::ICanIWriteBlock()
    1106             : {
    1107        5664 :     if (!GetUpdate())
    1108             :     {
    1109           0 :         CPLError(
    1110             :             CE_Failure, CPLE_NotSupported,
    1111             :             "IWriteBlock() not supported on dataset opened in read-only mode");
    1112           0 :         return false;
    1113             :     }
    1114             : 
    1115        5664 :     if (m_pabyCachedTiles == nullptr)
    1116             :     {
    1117           0 :         return false;
    1118             :     }
    1119             : 
    1120        5664 :     if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
    1121             :     {
    1122           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1123             :                  "IWriteBlock() not supported if georeferencing not set");
    1124           0 :         return false;
    1125             :     }
    1126        5664 :     return true;
    1127             : }
    1128             : 
    1129             : /************************************************************************/
    1130             : /*                            IRasterIO()                               */
    1131             : /************************************************************************/
    1132             : 
    1133         112 : CPLErr GDALGeoPackageDataset::IRasterIO(
    1134             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    1135             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    1136             :     int nBandCount, int *panBandMap, GSpacing nPixelSpace, GSpacing nLineSpace,
    1137             :     GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
    1138             : 
    1139             : {
    1140         112 :     CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
    1141             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
    1142             :         eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
    1143             :         psExtraArg);
    1144             : 
    1145             :     // If writing all bands, in non-shifted mode, flush all entirely written
    1146             :     // tiles This can avoid "stressing" the block cache with too many dirty
    1147             :     // blocks. Note: this logic would be useless with a per-dataset block cache.
    1148         112 :     if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
    1149         103 :         nYSize == nBufYSize && nBandCount == nBands &&
    1150         100 :         m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
    1151             :     {
    1152             :         auto poBand =
    1153          96 :             cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
    1154             :         int nBlockXSize, nBlockYSize;
    1155          96 :         poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1156          96 :         const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
    1157          96 :         const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
    1158          96 :         const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
    1159          96 :         const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
    1160         248 :         for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
    1161             :         {
    1162        4361 :             for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
    1163             :             {
    1164             :                 GDALRasterBlock *poBlock =
    1165        4209 :                     poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
    1166        4209 :                 if (poBlock)
    1167             :                 {
    1168             :                     // GetDirty() should be true in most situation (otherwise
    1169             :                     // it means the block cache is under extreme pressure!)
    1170        4203 :                     if (poBlock->GetDirty())
    1171             :                     {
    1172             :                         // IWriteBlock() on one band will check the dirty state
    1173             :                         // of the corresponding blocks in other bands, to decide
    1174             :                         // if it can call WriteTile(), so we have only to do
    1175             :                         // that on one of the bands
    1176        4203 :                         if (poBlock->Write() != CE_None)
    1177         250 :                             eErr = CE_Failure;
    1178             :                     }
    1179        4203 :                     poBlock->DropLock();
    1180             :                 }
    1181             :             }
    1182             :         }
    1183             :     }
    1184             : 
    1185         112 :     return eErr;
    1186             : }
    1187             : 
    1188             : /************************************************************************/
    1189             : /*                          GetOGRTableLimit()                          */
    1190             : /************************************************************************/
    1191             : 
    1192        2967 : static int GetOGRTableLimit()
    1193             : {
    1194        2967 :     return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
    1195             : }
    1196             : 
    1197             : /************************************************************************/
    1198             : /*                      GetNameTypeMapFromSQliteMaster()                */
    1199             : /************************************************************************/
    1200             : 
    1201             : const std::map<CPLString, CPLString> &
    1202         945 : GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
    1203             : {
    1204         945 :     if (!m_oMapNameToType.empty())
    1205         282 :         return m_oMapNameToType;
    1206             : 
    1207             :     CPLString osSQL(
    1208             :         "SELECT name, type FROM sqlite_master WHERE "
    1209             :         "type IN ('view', 'table') OR "
    1210        1326 :         "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
    1211         663 :     const int nTableLimit = GetOGRTableLimit();
    1212         663 :     if (nTableLimit > 0)
    1213             :     {
    1214         663 :         osSQL += " LIMIT ";
    1215         663 :         osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
    1216             :     }
    1217             : 
    1218         663 :     auto oResult = SQLQuery(hDB, osSQL);
    1219         663 :     if (oResult)
    1220             :     {
    1221       10958 :         for (int i = 0; i < oResult->RowCount(); i++)
    1222             :         {
    1223       10295 :             const char *pszName = oResult->GetValue(0, i);
    1224       10295 :             const char *pszType = oResult->GetValue(1, i);
    1225       10295 :             m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
    1226             :         }
    1227             :     }
    1228             : 
    1229         663 :     return m_oMapNameToType;
    1230             : }
    1231             : 
    1232             : /************************************************************************/
    1233             : /*                    RemoveTableFromSQLiteMasterCache()                */
    1234             : /************************************************************************/
    1235             : 
    1236          51 : void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
    1237             :     const char *pszTableName)
    1238             : {
    1239          51 :     m_oMapNameToType.erase(CPLString(pszTableName).toupper());
    1240          51 : }
    1241             : 
    1242             : /************************************************************************/
    1243             : /*                  GetUnknownExtensionsTableSpecific()                 */
    1244             : /************************************************************************/
    1245             : 
    1246             : const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
    1247         630 : GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
    1248             : {
    1249         630 :     if (m_bMapTableToExtensionsBuilt)
    1250          67 :         return m_oMapTableToExtensions;
    1251         563 :     m_bMapTableToExtensionsBuilt = true;
    1252             : 
    1253         563 :     if (!HasExtensionsTable())
    1254          33 :         return m_oMapTableToExtensions;
    1255             : 
    1256             :     CPLString osSQL(
    1257             :         "SELECT table_name, extension_name, definition, scope "
    1258             :         "FROM gpkg_extensions WHERE "
    1259             :         "table_name IS NOT NULL "
    1260             :         "AND extension_name IS NOT NULL "
    1261             :         "AND definition IS NOT NULL "
    1262             :         "AND scope IS NOT NULL "
    1263             :         "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
    1264             :         "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
    1265             :         "'gpkg_geom_MULTICURVE', "
    1266             :         "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
    1267             :         "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
    1268             :         "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
    1269             :         "'gpkg_srs_id_trigger', "
    1270             :         "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
    1271             :         "'gpkg_related_tables', 'related_tables'"
    1272             : #ifdef HAVE_SPATIALITE
    1273             :         ", 'gdal_spatialite_computed_geom_column'"
    1274             : #endif
    1275        1060 :         ")");
    1276         530 :     const int nTableLimit = GetOGRTableLimit();
    1277         530 :     if (nTableLimit > 0)
    1278             :     {
    1279         530 :         osSQL += " LIMIT ";
    1280         530 :         osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
    1281             :     }
    1282             : 
    1283         530 :     auto oResult = SQLQuery(hDB, osSQL);
    1284         530 :     if (oResult)
    1285             :     {
    1286         987 :         for (int i = 0; i < oResult->RowCount(); i++)
    1287             :         {
    1288         457 :             const char *pszTableName = oResult->GetValue(0, i);
    1289         457 :             const char *pszExtensionName = oResult->GetValue(1, i);
    1290         457 :             const char *pszDefinition = oResult->GetValue(2, i);
    1291         457 :             const char *pszScope = oResult->GetValue(3, i);
    1292         457 :             if (pszTableName && pszExtensionName && pszDefinition && pszScope)
    1293             :             {
    1294         457 :                 GPKGExtensionDesc oDesc;
    1295         457 :                 oDesc.osExtensionName = pszExtensionName;
    1296         457 :                 oDesc.osDefinition = pszDefinition;
    1297         457 :                 oDesc.osScope = pszScope;
    1298         914 :                 m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
    1299         457 :                     .push_back(oDesc);
    1300             :             }
    1301             :         }
    1302             :     }
    1303             : 
    1304         530 :     return m_oMapTableToExtensions;
    1305             : }
    1306             : 
    1307             : /************************************************************************/
    1308             : /*                           GetContents()                              */
    1309             : /************************************************************************/
    1310             : 
    1311             : const std::map<CPLString, GPKGContentsDesc> &
    1312         617 : GDALGeoPackageDataset::GetContents()
    1313             : {
    1314         617 :     if (m_bMapTableToContentsBuilt)
    1315          56 :         return m_oMapTableToContents;
    1316         561 :     m_bMapTableToContentsBuilt = true;
    1317             : 
    1318             :     CPLString osSQL("SELECT table_name, data_type, identifier, "
    1319             :                     "description, min_x, min_y, max_x, max_y "
    1320        1122 :                     "FROM gpkg_contents");
    1321         561 :     const int nTableLimit = GetOGRTableLimit();
    1322         561 :     if (nTableLimit > 0)
    1323             :     {
    1324         561 :         osSQL += " LIMIT ";
    1325         561 :         osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1326             :     }
    1327             : 
    1328         561 :     auto oResult = SQLQuery(hDB, osSQL);
    1329         561 :     if (oResult)
    1330             :     {
    1331        1209 :         for (int i = 0; i < oResult->RowCount(); i++)
    1332             :         {
    1333         648 :             const char *pszTableName = oResult->GetValue(0, i);
    1334         648 :             if (pszTableName == nullptr)
    1335           0 :                 continue;
    1336         648 :             const char *pszDataType = oResult->GetValue(1, i);
    1337         648 :             const char *pszIdentifier = oResult->GetValue(2, i);
    1338         648 :             const char *pszDescription = oResult->GetValue(3, i);
    1339         648 :             const char *pszMinX = oResult->GetValue(4, i);
    1340         648 :             const char *pszMinY = oResult->GetValue(5, i);
    1341         648 :             const char *pszMaxX = oResult->GetValue(6, i);
    1342         648 :             const char *pszMaxY = oResult->GetValue(7, i);
    1343         648 :             GPKGContentsDesc oDesc;
    1344         648 :             if (pszDataType)
    1345         648 :                 oDesc.osDataType = pszDataType;
    1346         648 :             if (pszIdentifier)
    1347         648 :                 oDesc.osIdentifier = pszIdentifier;
    1348         648 :             if (pszDescription)
    1349         647 :                 oDesc.osDescription = pszDescription;
    1350         648 :             if (pszMinX)
    1351         430 :                 oDesc.osMinX = pszMinX;
    1352         648 :             if (pszMinY)
    1353         430 :                 oDesc.osMinY = pszMinY;
    1354         648 :             if (pszMaxX)
    1355         430 :                 oDesc.osMaxX = pszMaxX;
    1356         648 :             if (pszMaxY)
    1357         430 :                 oDesc.osMaxY = pszMaxY;
    1358        1296 :             m_oMapTableToContents[CPLString(pszTableName).toupper()] =
    1359        1296 :                 std::move(oDesc);
    1360             :         }
    1361             :     }
    1362             : 
    1363         561 :     return m_oMapTableToContents;
    1364             : }
    1365             : 
    1366             : /************************************************************************/
    1367             : /*                                Open()                                */
    1368             : /************************************************************************/
    1369             : 
    1370         962 : int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
    1371             :                                 const std::string &osFilenameInZip)
    1372             : {
    1373         962 :     m_osFilenameInZip = osFilenameInZip;
    1374         962 :     CPLAssert(m_nLayers == 0);
    1375         962 :     CPLAssert(hDB == nullptr);
    1376             : 
    1377         962 :     SetDescription(poOpenInfo->pszFilename);
    1378        1924 :     CPLString osFilename(poOpenInfo->pszFilename);
    1379        1924 :     CPLString osSubdatasetTableName;
    1380             :     GByte abyHeaderLetMeHerePlease[100];
    1381         962 :     const GByte *pabyHeader = poOpenInfo->pabyHeader;
    1382         962 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
    1383             :     {
    1384         166 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
    1385             :                                                 CSLT_HONOURSTRINGS);
    1386         166 :         int nCount = CSLCount(papszTokens);
    1387         166 :         if (nCount < 2)
    1388             :         {
    1389           0 :             CSLDestroy(papszTokens);
    1390           0 :             return FALSE;
    1391             :         }
    1392             : 
    1393         166 :         if (nCount <= 3)
    1394             :         {
    1395         164 :             osFilename = papszTokens[1];
    1396             :         }
    1397             :         /* GPKG:C:\BLA.GPKG:foo */
    1398           2 :         else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
    1399           2 :                  (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
    1400             :         {
    1401           2 :             osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1402             :         }
    1403             :         // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
    1404           0 :         else if (/*nCount >= 4 && */
    1405           0 :                  (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1406           0 :                   EQUAL(papszTokens[1], "/vsicurl/https")))
    1407             :         {
    1408           0 :             osFilename = CPLString(papszTokens[1]);
    1409           0 :             for (int i = 2; i < nCount - 1; i++)
    1410             :             {
    1411           0 :                 osFilename += ':';
    1412           0 :                 osFilename += papszTokens[i];
    1413             :             }
    1414             :         }
    1415         166 :         if (nCount >= 3)
    1416          12 :             osSubdatasetTableName = papszTokens[nCount - 1];
    1417             : 
    1418         166 :         CSLDestroy(papszTokens);
    1419         166 :         VSILFILE *fp = VSIFOpenL(osFilename, "rb");
    1420         166 :         if (fp != nullptr)
    1421             :         {
    1422         166 :             VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
    1423         166 :             VSIFCloseL(fp);
    1424             :         }
    1425         166 :         pabyHeader = abyHeaderLetMeHerePlease;
    1426             :     }
    1427         796 :     else if (poOpenInfo->pabyHeader &&
    1428         796 :              STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1429             :                          "SQLite format 3"))
    1430             :     {
    1431         790 :         m_bCallUndeclareFileNotToOpen = true;
    1432         790 :         GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
    1433             :                                          poOpenInfo->nHeaderBytes);
    1434             :     }
    1435             : 
    1436         962 :     eAccess = poOpenInfo->eAccess;
    1437         962 :     if (!m_osFilenameInZip.empty())
    1438             :     {
    1439           1 :         m_pszFilename = CPLStrdup(CPLSPrintf(
    1440             :             "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
    1441             :     }
    1442             :     else
    1443             :     {
    1444         961 :         m_pszFilename = CPLStrdup(osFilename);
    1445             :     }
    1446             : 
    1447         962 :     if (poOpenInfo->papszOpenOptions)
    1448             :     {
    1449          96 :         CSLDestroy(papszOpenOptions);
    1450          96 :         papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1451             :     }
    1452             : 
    1453             : #ifdef ENABLE_SQL_GPKG_FORMAT
    1454         962 :     if (poOpenInfo->pabyHeader &&
    1455         796 :         STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1456           5 :                     "-- SQL GPKG") &&
    1457           5 :         poOpenInfo->fpL != nullptr)
    1458             :     {
    1459           5 :         if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
    1460             :             SQLITE_OK)
    1461             :         {
    1462           0 :             return FALSE;
    1463             :         }
    1464             : 
    1465           5 :         InstallSQLFunctions();
    1466             : 
    1467             :         // Ingest the lines of the dump
    1468           5 :         VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
    1469             :         const char *pszLine;
    1470          76 :         while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
    1471             :         {
    1472          71 :             if (STARTS_WITH(pszLine, "--"))
    1473           5 :                 continue;
    1474             : 
    1475             :             // Reject a few words tat might have security implications
    1476             :             // Basically we just want to allow CREATE TABLE and INSERT INTO
    1477          66 :             if (CPLString(pszLine).ifind("ATTACH") != std::string::npos ||
    1478         132 :                 CPLString(pszLine).ifind("DETACH") != std::string::npos ||
    1479         132 :                 CPLString(pszLine).ifind("PRAGMA") != std::string::npos ||
    1480         132 :                 CPLString(pszLine).ifind("SELECT") != std::string::npos ||
    1481         128 :                 CPLString(pszLine).ifind("UPDATE") != std::string::npos ||
    1482         128 :                 CPLString(pszLine).ifind("REPLACE") != std::string::npos ||
    1483         128 :                 CPLString(pszLine).ifind("DELETE") != std::string::npos ||
    1484         128 :                 CPLString(pszLine).ifind("DROP") != std::string::npos ||
    1485         260 :                 CPLString(pszLine).ifind("ALTER") != std::string::npos ||
    1486         128 :                 CPLString(pszLine).ifind("VIRTUAL") != std::string::npos)
    1487             :             {
    1488           8 :                 bool bOK = false;
    1489             :                 // Accept creation of spatial index
    1490           8 :                 if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
    1491             :                 {
    1492           4 :                     const char *pszStr =
    1493             :                         pszLine + strlen("CREATE VIRTUAL TABLE ");
    1494           4 :                     if (*pszStr == '"')
    1495           0 :                         pszStr++;
    1496          52 :                     while ((*pszStr >= 'a' && *pszStr <= 'z') ||
    1497          64 :                            (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
    1498             :                     {
    1499          60 :                         pszStr++;
    1500             :                     }
    1501           4 :                     if (*pszStr == '"')
    1502           0 :                         pszStr++;
    1503           4 :                     if (EQUAL(pszStr,
    1504             :                               " USING rtree(id, minx, maxx, miny, maxy);"))
    1505             :                     {
    1506           4 :                         bOK = true;
    1507             :                     }
    1508             :                 }
    1509             :                 // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
    1510             :                 // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
    1511           8 :                 else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
    1512           8 :                          CPLString(pszLine).ifind("SELECT") !=
    1513             :                              std::string::npos)
    1514             :                 {
    1515             :                     char **papszTokens =
    1516           4 :                         CSLTokenizeString2(pszLine, " (),,", 0);
    1517           4 :                     if (CSLCount(papszTokens) == 15 &&
    1518           4 :                         EQUAL(papszTokens[3], "SELECT") &&
    1519           4 :                         EQUAL(papszTokens[5], "ST_MinX") &&
    1520           4 :                         EQUAL(papszTokens[7], "ST_MaxX") &&
    1521           4 :                         EQUAL(papszTokens[9], "ST_MinY") &&
    1522          12 :                         EQUAL(papszTokens[11], "ST_MaxY") &&
    1523           4 :                         EQUAL(papszTokens[13], "FROM"))
    1524             :                     {
    1525           4 :                         bOK = TRUE;
    1526             :                     }
    1527           4 :                     CSLDestroy(papszTokens);
    1528             :                 }
    1529             : 
    1530           8 :                 if (!bOK)
    1531             :                 {
    1532           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
    1533             :                              "Rejected statement: %s", pszLine);
    1534           0 :                     return FALSE;
    1535             :                 }
    1536             :             }
    1537          66 :             char *pszErrMsg = nullptr;
    1538          66 :             if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
    1539             :                 SQLITE_OK)
    1540             :             {
    1541           0 :                 if (pszErrMsg)
    1542           0 :                     CPLDebug("SQLITE", "Error %s", pszErrMsg);
    1543             :             }
    1544          66 :             sqlite3_free(pszErrMsg);
    1545           5 :         }
    1546             :     }
    1547             : 
    1548         957 :     else if (pabyHeader != nullptr)
    1549             : #endif
    1550             :     {
    1551         957 :         if (poOpenInfo->fpL)
    1552             :         {
    1553             :             // See above comment about -wal locking for the importance of
    1554             :             // closing that file, prior to calling sqlite3_open()
    1555         692 :             VSIFCloseL(poOpenInfo->fpL);
    1556         692 :             poOpenInfo->fpL = nullptr;
    1557             :         }
    1558             : 
    1559             :         /* See if we can open the SQLite database */
    1560         957 :         if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
    1561             :                                         : SQLITE_OPEN_READONLY))
    1562           2 :             return FALSE;
    1563             : 
    1564         955 :         memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
    1565         955 :         m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    1566         955 :         memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
    1567         955 :         m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    1568         955 :         if (m_nApplicationId == GP10_APPLICATION_ID)
    1569             :         {
    1570           7 :             CPLDebug("GPKG", "GeoPackage v1.0");
    1571             :         }
    1572         948 :         else if (m_nApplicationId == GP11_APPLICATION_ID)
    1573             :         {
    1574           2 :             CPLDebug("GPKG", "GeoPackage v1.1");
    1575             :         }
    1576         946 :         else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    1577         943 :                  m_nUserVersion >= GPKG_1_2_VERSION)
    1578             :         {
    1579         941 :             CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    1580         941 :                      (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    1581             :         }
    1582             :     }
    1583             : 
    1584             :     /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
    1585             :      * “ok” */
    1586             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1587             :     /* Disable integrity check by default, since it is expensive on big files */
    1588         960 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
    1589           0 :         OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
    1590             :     {
    1591           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1592             :                  "pragma integrity_check on '%s' failed", m_pszFilename);
    1593           0 :         return FALSE;
    1594             :     }
    1595             : 
    1596             :     /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
    1597             :     /* parameter value SHALL return an empty result set */
    1598             :     /* http://opengis.github.io/geopackage/#_file_integrity */
    1599             :     /* Disable the check by default, since it is to corrupt databases, and */
    1600             :     /* that causes issues to downstream software that can't open them. */
    1601         960 :     if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
    1602           0 :         OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
    1603             :     {
    1604           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1605             :                  "pragma foreign_key_check on '%s' failed.", m_pszFilename);
    1606           0 :         return FALSE;
    1607             :     }
    1608             : 
    1609             :     /* Check for requirement metadata tables */
    1610             :     /* Requirement 10: gpkg_spatial_ref_sys must exist */
    1611             :     /* Requirement 13: gpkg_contents must exist */
    1612         960 :     if (SQLGetInteger(hDB,
    1613             :                       "SELECT COUNT(*) FROM sqlite_master WHERE "
    1614             :                       "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
    1615             :                       "type IN ('table', 'view')",
    1616         960 :                       nullptr) != 2)
    1617             :     {
    1618           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1619             :                  "At least one of the required GeoPackage tables, "
    1620             :                  "gpkg_spatial_ref_sys or gpkg_contents, is missing");
    1621           0 :         return FALSE;
    1622             :     }
    1623             : 
    1624         960 :     DetectSpatialRefSysColumns();
    1625             : 
    1626             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    1627         960 :     if (SQLGetInteger(hDB,
    1628             :                       "SELECT 1 FROM sqlite_master WHERE "
    1629             :                       "name = 'gpkg_ogr_contents' AND type = 'table'",
    1630         960 :                       nullptr) == 1)
    1631             :     {
    1632         955 :         m_bHasGPKGOGRContents = true;
    1633             :     }
    1634             : #endif
    1635             : 
    1636         960 :     CheckUnknownExtensions();
    1637             : 
    1638         960 :     int bRet = FALSE;
    1639         960 :     bool bHasGPKGExtRelations = false;
    1640         960 :     if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    1641             :     {
    1642         783 :         m_bHasGPKGGeometryColumns =
    1643         783 :             SQLGetInteger(hDB,
    1644             :                           "SELECT 1 FROM sqlite_master WHERE "
    1645             :                           "name = 'gpkg_geometry_columns' AND "
    1646             :                           "type IN ('table', 'view')",
    1647         783 :                           nullptr) == 1;
    1648         783 :         bHasGPKGExtRelations = HasGpkgextRelationsTable();
    1649             :     }
    1650         960 :     if (m_bHasGPKGGeometryColumns)
    1651             :     {
    1652             :         /* Load layer definitions for all tables in gpkg_contents &
    1653             :          * gpkg_geometry_columns */
    1654             :         /* and non-spatial tables as well */
    1655             :         std::string osSQL =
    1656             :             "SELECT c.table_name, c.identifier, 1 as is_spatial, "
    1657             :             "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
    1658             :             "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
    1659             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1660             :             "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
    1661             :             "  FROM gpkg_geometry_columns g "
    1662             :             "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
    1663             :             "  WHERE "
    1664             :             "  c.table_name <> 'ogr_empty_table' AND"
    1665             :             "  c.data_type = 'features' "
    1666             :             // aspatial: Was the only method available in OGR 2.0 and 2.1
    1667             :             // attributes: GPKG 1.2 or later
    1668             :             "UNION ALL "
    1669             :             "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
    1670             :             "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
    1671             :             "is_in_gpkg_contents, "
    1672             :             "(SELECT type FROM sqlite_master WHERE lower(name) = "
    1673             :             "lower(table_name) AND type IN ('table', 'view')) AS object_type "
    1674             :             "  FROM gpkg_contents"
    1675         782 :             "  WHERE data_type IN ('aspatial', 'attributes') ";
    1676             : 
    1677        1564 :         const char *pszListAllTables = CSLFetchNameValueDef(
    1678         782 :             poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
    1679         782 :         bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
    1680         782 :         if (!bHasASpatialOrAttributes)
    1681             :         {
    1682             :             auto oResultTable =
    1683             :                 SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
    1684         781 :                               "data_type = 'attributes' LIMIT 1");
    1685         781 :             bHasASpatialOrAttributes =
    1686         781 :                 (oResultTable && oResultTable->RowCount() == 1);
    1687             :         }
    1688         782 :         if (bHasGPKGExtRelations)
    1689             :         {
    1690             :             osSQL += "UNION ALL "
    1691             :                      "SELECT mapping_table_name, mapping_table_name, 0 as "
    1692             :                      "is_spatial, NULL, NULL, 0, 0, 0 AS "
    1693             :                      "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1694             :                      "is_in_gpkg_contents, 'table' AS object_type "
    1695             :                      "FROM gpkgext_relations WHERE "
    1696             :                      "lower(mapping_table_name) NOT IN (SELECT "
    1697             :                      "lower(table_name) FROM "
    1698          12 :                      "gpkg_contents)";
    1699             :         }
    1700         782 :         if (EQUAL(pszListAllTables, "YES") ||
    1701         782 :             (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
    1702             :         {
    1703             :             // vgpkg_ is Spatialite virtual table
    1704             :             osSQL +=
    1705             :                 "UNION ALL "
    1706             :                 "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
    1707             :                 "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
    1708             :                 "is_in_gpkg_contents, type AS object_type "
    1709             :                 "FROM sqlite_master WHERE type IN ('table', 'view') "
    1710             :                 "AND name NOT LIKE 'gpkg_%' "
    1711             :                 "AND name NOT LIKE 'vgpkg_%' "
    1712             :                 "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
    1713             :                 // Avoid reading those views from simple_sewer_features.gpkg
    1714             :                 "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
    1715             :                 "'st_geometry_columns', 'geometry_columns') "
    1716             :                 "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
    1717         738 :                 "gpkg_contents)";
    1718         738 :             if (bHasGPKGExtRelations)
    1719             :             {
    1720             :                 osSQL += " AND lower(name) NOT IN (SELECT "
    1721             :                          "lower(mapping_table_name) FROM "
    1722           8 :                          "gpkgext_relations)";
    1723             :             }
    1724             :         }
    1725         782 :         const int nTableLimit = GetOGRTableLimit();
    1726         782 :         if (nTableLimit > 0)
    1727             :         {
    1728         782 :             osSQL += " LIMIT ";
    1729         782 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1730             :         }
    1731             : 
    1732         782 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1733         782 :         if (!oResult)
    1734             :         {
    1735           0 :             return FALSE;
    1736             :         }
    1737             : 
    1738         782 :         if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1739             :         {
    1740           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    1741             :                      "File has more than %d vector tables. "
    1742             :                      "Limiting to first %d (can be overridden with "
    1743             :                      "OGR_TABLE_LIMIT config option)",
    1744             :                      nTableLimit, nTableLimit);
    1745           1 :             oResult->LimitRowCount(nTableLimit);
    1746             :         }
    1747             : 
    1748         782 :         if (oResult->RowCount() > 0)
    1749             :         {
    1750         673 :             bRet = TRUE;
    1751             : 
    1752        1346 :             m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLMalloc(
    1753         673 :                 sizeof(OGRGeoPackageTableLayer *) * oResult->RowCount()));
    1754             : 
    1755        1346 :             std::map<std::string, int> oMapTableRefCount;
    1756        3496 :             for (int i = 0; i < oResult->RowCount(); i++)
    1757             :             {
    1758        2823 :                 const char *pszTableName = oResult->GetValue(0, i);
    1759        2823 :                 if (pszTableName == nullptr)
    1760           0 :                     continue;
    1761        2823 :                 if (++oMapTableRefCount[pszTableName] == 2)
    1762             :                 {
    1763             :                     // This should normally not happen if all constraints are
    1764             :                     // properly set
    1765           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1766             :                              "Table %s appearing several times in "
    1767             :                              "gpkg_contents and/or gpkg_geometry_columns",
    1768             :                              pszTableName);
    1769             :                 }
    1770             :             }
    1771             : 
    1772        1346 :             std::set<std::string> oExistingLayers;
    1773        3496 :             for (int i = 0; i < oResult->RowCount(); i++)
    1774             :             {
    1775        2823 :                 const char *pszTableName = oResult->GetValue(0, i);
    1776        2823 :                 if (pszTableName == nullptr)
    1777           2 :                     continue;
    1778             :                 const bool bTableHasSeveralGeomColumns =
    1779        2823 :                     oMapTableRefCount[pszTableName] > 1;
    1780        2823 :                 bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
    1781        2823 :                 const char *pszGeomColName = oResult->GetValue(3, i);
    1782        2823 :                 const char *pszGeomType = oResult->GetValue(4, i);
    1783        2823 :                 const char *pszZ = oResult->GetValue(5, i);
    1784        2823 :                 const char *pszM = oResult->GetValue(6, i);
    1785             :                 bool bIsInGpkgContents =
    1786        2823 :                     CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
    1787        2823 :                 if (!bIsInGpkgContents)
    1788          40 :                     m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
    1789        2823 :                 const char *pszObjectType = oResult->GetValue(12, i);
    1790        2823 :                 if (pszObjectType == nullptr ||
    1791        2822 :                     !(EQUAL(pszObjectType, "table") ||
    1792          21 :                       EQUAL(pszObjectType, "view")))
    1793             :                 {
    1794           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1795             :                              "Table/view %s is referenced in gpkg_contents, "
    1796             :                              "but does not exist",
    1797             :                              pszTableName);
    1798           1 :                     continue;
    1799             :                 }
    1800             :                 // Non-standard and undocumented behavior:
    1801             :                 // if the same table appears to have several geometry columns,
    1802             :                 // handle it for now as multiple layers named
    1803             :                 // "table_name (geom_col_name)"
    1804             :                 // The way we handle that might change in the future (e.g
    1805             :                 // could be a single layer with multiple geometry columns)
    1806             :                 const std::string osLayerNameWithGeomColName =
    1807        5021 :                     pszGeomColName ? std::string(pszTableName) + " (" +
    1808             :                                          pszGeomColName + ')'
    1809        5644 :                                    : std::string(pszTableName);
    1810        2822 :                 if (oExistingLayers.find(osLayerNameWithGeomColName) !=
    1811        5644 :                     oExistingLayers.end())
    1812           1 :                     continue;
    1813        2821 :                 oExistingLayers.insert(osLayerNameWithGeomColName);
    1814             :                 const std::string osLayerName = bTableHasSeveralGeomColumns
    1815             :                                                     ? osLayerNameWithGeomColName
    1816        2821 :                                                     : std::string(pszTableName);
    1817             :                 OGRGeoPackageTableLayer *poLayer =
    1818        2821 :                     new OGRGeoPackageTableLayer(this, osLayerName.c_str());
    1819        2821 :                 bool bHasZ = pszZ && atoi(pszZ) > 0;
    1820        2821 :                 bool bHasM = pszM && atoi(pszM) > 0;
    1821        2821 :                 if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
    1822             :                 {
    1823         457 :                     if (pszZ && atoi(pszZ) == 2)
    1824           7 :                         bHasZ = false;
    1825         457 :                     if (pszM && atoi(pszM) == 2)
    1826           6 :                         bHasM = false;
    1827             :                 }
    1828        2821 :                 poLayer->SetOpeningParameters(
    1829             :                     pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
    1830             :                     pszGeomColName, pszGeomType, bHasZ, bHasM);
    1831        2821 :                 m_papoLayers[m_nLayers++] = poLayer;
    1832             :             }
    1833             :         }
    1834             :     }
    1835             : 
    1836         960 :     bool bHasTileMatrixSet = false;
    1837         960 :     if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
    1838             :     {
    1839         432 :         bHasTileMatrixSet = SQLGetInteger(hDB,
    1840             :                                           "SELECT 1 FROM sqlite_master WHERE "
    1841             :                                           "name = 'gpkg_tile_matrix_set' AND "
    1842             :                                           "type IN ('table', 'view')",
    1843             :                                           nullptr) == 1;
    1844             :     }
    1845         960 :     if (bHasTileMatrixSet)
    1846             :     {
    1847             :         std::string osSQL =
    1848             :             "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
    1849             :             "c.min_x, c.min_y, c.max_x, c.max_y, "
    1850             :             "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
    1851             :             "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
    1852             :             "c.table_name = tms.table_name WHERE "
    1853         431 :             "data_type IN ('tiles', '2d-gridded-coverage')";
    1854         431 :         if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
    1855             :             osSubdatasetTableName =
    1856           2 :                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
    1857         431 :         if (!osSubdatasetTableName.empty())
    1858             :         {
    1859          14 :             char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
    1860             :                                            osSubdatasetTableName.c_str());
    1861          14 :             osSQL += pszTmp;
    1862          14 :             sqlite3_free(pszTmp);
    1863          14 :             SetPhysicalFilename(osFilename.c_str());
    1864             :         }
    1865         431 :         const int nTableLimit = GetOGRTableLimit();
    1866         431 :         if (nTableLimit > 0)
    1867             :         {
    1868         431 :             osSQL += " LIMIT ";
    1869         431 :             osSQL += CPLSPrintf("%d", 1 + nTableLimit);
    1870             :         }
    1871             : 
    1872         431 :         auto oResult = SQLQuery(hDB, osSQL.c_str());
    1873         431 :         if (!oResult)
    1874             :         {
    1875           0 :             return FALSE;
    1876             :         }
    1877             : 
    1878         431 :         if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
    1879             :         {
    1880           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1881             :                      "Cannot find table '%s' in GeoPackage dataset",
    1882             :                      osSubdatasetTableName.c_str());
    1883             :         }
    1884         430 :         else if (oResult->RowCount() == 1)
    1885             :         {
    1886         258 :             const char *pszTableName = oResult->GetValue(0, 0);
    1887         258 :             const char *pszIdentifier = oResult->GetValue(1, 0);
    1888         258 :             const char *pszDescription = oResult->GetValue(2, 0);
    1889         258 :             const char *pszSRSId = oResult->GetValue(3, 0);
    1890         258 :             const char *pszMinX = oResult->GetValue(4, 0);
    1891         258 :             const char *pszMinY = oResult->GetValue(5, 0);
    1892         258 :             const char *pszMaxX = oResult->GetValue(6, 0);
    1893         258 :             const char *pszMaxY = oResult->GetValue(7, 0);
    1894         258 :             const char *pszTMSMinX = oResult->GetValue(8, 0);
    1895         258 :             const char *pszTMSMinY = oResult->GetValue(9, 0);
    1896         258 :             const char *pszTMSMaxX = oResult->GetValue(10, 0);
    1897         258 :             const char *pszTMSMaxY = oResult->GetValue(11, 0);
    1898         258 :             const char *pszDataType = oResult->GetValue(12, 0);
    1899         258 :             if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
    1900             :                 pszTMSMaxY)
    1901             :             {
    1902         516 :                 bRet = OpenRaster(
    1903             :                     pszTableName, pszIdentifier, pszDescription,
    1904         258 :                     pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
    1905             :                     CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
    1906             :                     CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
    1907         258 :                     EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
    1908             :             }
    1909             :         }
    1910         172 :         else if (oResult->RowCount() >= 1)
    1911             :         {
    1912           4 :             bRet = TRUE;
    1913             : 
    1914           4 :             if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
    1915             :             {
    1916           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1917             :                          "File has more than %d raster tables. "
    1918             :                          "Limiting to first %d (can be overridden with "
    1919             :                          "OGR_TABLE_LIMIT config option)",
    1920             :                          nTableLimit, nTableLimit);
    1921           1 :                 oResult->LimitRowCount(nTableLimit);
    1922             :             }
    1923             : 
    1924           4 :             int nSDSCount = 0;
    1925        2010 :             for (int i = 0; i < oResult->RowCount(); i++)
    1926             :             {
    1927        2006 :                 const char *pszTableName = oResult->GetValue(0, i);
    1928        2006 :                 const char *pszIdentifier = oResult->GetValue(1, i);
    1929        2006 :                 if (pszTableName == nullptr)
    1930           0 :                     continue;
    1931             :                 m_aosSubDatasets.AddNameValue(
    1932             :                     CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
    1933        2006 :                     CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
    1934             :                 m_aosSubDatasets.AddNameValue(
    1935             :                     CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
    1936             :                     pszIdentifier
    1937        2006 :                         ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
    1938        4012 :                         : pszTableName);
    1939        2006 :                 nSDSCount++;
    1940             :             }
    1941             :         }
    1942             :     }
    1943             : 
    1944         960 :     if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
    1945             :     {
    1946          30 :         if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
    1947             :         {
    1948          20 :             bRet = TRUE;
    1949             :         }
    1950             :         else
    1951             :         {
    1952          10 :             CPLDebug("GPKG",
    1953             :                      "This GeoPackage has no vector content and is opened "
    1954             :                      "in read-only mode. If you open it in update mode, "
    1955             :                      "opening will be successful.");
    1956             :         }
    1957             :     }
    1958             : 
    1959         960 :     if (eAccess == GA_Update)
    1960             :     {
    1961         195 :         FixupWrongRTreeTrigger();
    1962         195 :         FixupWrongMedataReferenceColumnNameUpdate();
    1963             :     }
    1964             : 
    1965         960 :     SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    1966             : 
    1967         960 :     return bRet;
    1968             : }
    1969             : 
    1970             : /************************************************************************/
    1971             : /*                    DetectSpatialRefSysColumns()                      */
    1972             : /************************************************************************/
    1973             : 
    1974         967 : void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
    1975             : {
    1976             :     // Detect definition_12_063 column
    1977             :     {
    1978         967 :         sqlite3_stmt *hSQLStmt = nullptr;
    1979         967 :         int rc = sqlite3_prepare_v2(
    1980             :             hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
    1981             :             &hSQLStmt, nullptr);
    1982         967 :         if (rc == SQLITE_OK)
    1983             :         {
    1984          78 :             m_bHasDefinition12_063 = true;
    1985          78 :             sqlite3_finalize(hSQLStmt);
    1986             :         }
    1987             :     }
    1988             : 
    1989             :     // Detect epoch column
    1990         967 :     if (m_bHasDefinition12_063)
    1991             :     {
    1992          78 :         sqlite3_stmt *hSQLStmt = nullptr;
    1993             :         int rc =
    1994          78 :             sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
    1995             :                                -1, &hSQLStmt, nullptr);
    1996          78 :         if (rc == SQLITE_OK)
    1997             :         {
    1998           5 :             m_bHasEpochColumn = true;
    1999           5 :             sqlite3_finalize(hSQLStmt);
    2000             :         }
    2001             :     }
    2002         967 : }
    2003             : 
    2004             : /************************************************************************/
    2005             : /*                    FixupWrongRTreeTrigger()                          */
    2006             : /************************************************************************/
    2007             : 
    2008         195 : void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
    2009             : {
    2010             :     auto oResult = SQLQuery(
    2011             :         hDB,
    2012             :         "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
    2013         195 :         "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
    2014         195 :     if (oResult == nullptr)
    2015           0 :         return;
    2016         195 :     if (oResult->RowCount() > 0)
    2017             :     {
    2018           1 :         CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
    2019             :     }
    2020         197 :     for (int i = 0; i < oResult->RowCount(); i++)
    2021             :     {
    2022           2 :         const char *pszName = oResult->GetValue(0, i);
    2023           2 :         const char *pszSQL = oResult->GetValue(1, i);
    2024           2 :         const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
    2025           2 :         if (pszPtr1)
    2026             :         {
    2027           2 :             const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
    2028             :             // Skipping over geometry column name
    2029           4 :             while (*pszPtr == ' ')
    2030           2 :                 pszPtr++;
    2031           2 :             if (pszPtr[0] == '"' || pszPtr[0] == '\'')
    2032             :             {
    2033           1 :                 char chStringDelim = pszPtr[0];
    2034           1 :                 pszPtr++;
    2035           9 :                 while (*pszPtr != '\0' && *pszPtr != chStringDelim)
    2036             :                 {
    2037           8 :                     if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
    2038           0 :                         pszPtr += 2;
    2039             :                     else
    2040           8 :                         pszPtr += 1;
    2041             :                 }
    2042           1 :                 if (*pszPtr == chStringDelim)
    2043           1 :                     pszPtr++;
    2044             :             }
    2045             :             else
    2046             :             {
    2047           1 :                 pszPtr++;
    2048           8 :                 while (*pszPtr != ' ')
    2049           7 :                     pszPtr++;
    2050             :             }
    2051           2 :             if (*pszPtr == ' ')
    2052             :             {
    2053           2 :                 SQLCommand(hDB,
    2054           4 :                            ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
    2055             :                                .c_str());
    2056           4 :                 CPLString newSQL;
    2057           2 :                 newSQL.assign(pszSQL, pszPtr1 - pszSQL);
    2058           2 :                 newSQL += " AFTER UPDATE";
    2059           2 :                 newSQL += pszPtr;
    2060           2 :                 SQLCommand(hDB, newSQL);
    2061             :             }
    2062             :         }
    2063             :     }
    2064             : }
    2065             : 
    2066             : /************************************************************************/
    2067             : /*             FixupWrongMedataReferenceColumnNameUpdate()              */
    2068             : /************************************************************************/
    2069             : 
    2070         195 : void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
    2071             : {
    2072             :     // Fix wrong trigger that was generated by GDAL < 2.4.0
    2073             :     // See https://github.com/qgis/QGIS/issues/42768
    2074             :     auto oResult = SQLQuery(
    2075             :         hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
    2076             :              "NAME ='gpkg_metadata_reference_column_name_update' AND "
    2077         195 :              "sql LIKE '%column_nameIS%'");
    2078         195 :     if (oResult == nullptr)
    2079           0 :         return;
    2080         195 :     if (oResult->RowCount() == 1)
    2081             :     {
    2082           1 :         CPLDebug("GPKG", "Fixing incorrect trigger "
    2083             :                          "gpkg_metadata_reference_column_name_update");
    2084           1 :         const char *pszSQL = oResult->GetValue(0, 0);
    2085             :         std::string osNewSQL(
    2086           3 :             CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
    2087             : 
    2088           1 :         SQLCommand(hDB,
    2089             :                    "DROP TRIGGER gpkg_metadata_reference_column_name_update");
    2090           1 :         SQLCommand(hDB, osNewSQL.c_str());
    2091             :     }
    2092             : }
    2093             : 
    2094             : /************************************************************************/
    2095             : /*                  ClearCachedRelationships()                          */
    2096             : /************************************************************************/
    2097             : 
    2098          35 : void GDALGeoPackageDataset::ClearCachedRelationships()
    2099             : {
    2100          35 :     m_bHasPopulatedRelationships = false;
    2101          35 :     m_osMapRelationships.clear();
    2102          35 : }
    2103             : 
    2104             : /************************************************************************/
    2105             : /*                           LoadRelationships()                        */
    2106             : /************************************************************************/
    2107             : 
    2108          49 : void GDALGeoPackageDataset::LoadRelationships() const
    2109             : {
    2110          49 :     m_osMapRelationships.clear();
    2111             : 
    2112          49 :     std::vector<std::string> oExcludedTables;
    2113          49 :     if (HasGpkgextRelationsTable())
    2114             :     {
    2115          30 :         LoadRelationshipsUsingRelatedTablesExtension();
    2116             : 
    2117          76 :         for (const auto &oRelationship : m_osMapRelationships)
    2118             :         {
    2119             :             oExcludedTables.emplace_back(
    2120          46 :                 oRelationship.second->GetMappingTableName());
    2121             :         }
    2122             :     }
    2123             : 
    2124             :     // Also load relationships defined using foreign keys (i.e. one-to-many
    2125             :     // relationships). Here we must exclude any relationships defined from the
    2126             :     // related tables extension, we don't want them included twice.
    2127          49 :     LoadRelationshipsFromForeignKeys(oExcludedTables);
    2128          49 :     m_bHasPopulatedRelationships = true;
    2129          49 : }
    2130             : 
    2131             : /************************************************************************/
    2132             : /*         LoadRelationshipsUsingRelatedTablesExtension()               */
    2133             : /************************************************************************/
    2134             : 
    2135          30 : void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
    2136             : {
    2137          30 :     m_osMapRelationships.clear();
    2138             : 
    2139             :     auto oResultTable = SQLQuery(
    2140          30 :         hDB, "SELECT base_table_name, base_primary_column, "
    2141             :              "related_table_name, related_primary_column, relation_name, "
    2142          60 :              "mapping_table_name FROM gpkgext_relations");
    2143          30 :     if (oResultTable && oResultTable->RowCount() > 0)
    2144             :     {
    2145          74 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    2146             :         {
    2147          47 :             const char *pszBaseTableName = oResultTable->GetValue(0, i);
    2148          47 :             if (!pszBaseTableName)
    2149             :             {
    2150           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2151             :                          "Could not retrieve base_table_name from "
    2152             :                          "gpkgext_relations");
    2153           1 :                 continue;
    2154             :             }
    2155          47 :             const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
    2156          47 :             if (!pszBasePrimaryColumn)
    2157             :             {
    2158           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2159             :                          "Could not retrieve base_primary_column from "
    2160             :                          "gpkgext_relations");
    2161           0 :                 continue;
    2162             :             }
    2163          47 :             const char *pszRelatedTableName = oResultTable->GetValue(2, i);
    2164          47 :             if (!pszRelatedTableName)
    2165             :             {
    2166           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2167             :                          "Could not retrieve related_table_name from "
    2168             :                          "gpkgext_relations");
    2169           0 :                 continue;
    2170             :             }
    2171          47 :             const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
    2172          47 :             if (!pszRelatedPrimaryColumn)
    2173             :             {
    2174           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2175             :                          "Could not retrieve related_primary_column from "
    2176             :                          "gpkgext_relations");
    2177           0 :                 continue;
    2178             :             }
    2179          47 :             const char *pszRelationName = oResultTable->GetValue(4, i);
    2180          47 :             if (!pszRelationName)
    2181             :             {
    2182           0 :                 CPLError(
    2183             :                     CE_Warning, CPLE_AppDefined,
    2184             :                     "Could not retrieve relation_name from gpkgext_relations");
    2185           0 :                 continue;
    2186             :             }
    2187          47 :             const char *pszMappingTableName = oResultTable->GetValue(5, i);
    2188          47 :             if (!pszMappingTableName)
    2189             :             {
    2190           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2191             :                          "Could not retrieve mapping_table_name from "
    2192             :                          "gpkgext_relations");
    2193           0 :                 continue;
    2194             :             }
    2195             : 
    2196             :             // confirm that mapping table exists
    2197             :             char *pszSQL =
    2198          47 :                 sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
    2199             :                                 "name='%q' AND type IN ('table', 'view')",
    2200             :                                 pszMappingTableName);
    2201          47 :             const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
    2202          47 :             sqlite3_free(pszSQL);
    2203             : 
    2204          47 :             if (nMappingTableCount < 1)
    2205             :             {
    2206           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2207             :                          "Relationship mapping table %s does not exist",
    2208             :                          pszMappingTableName);
    2209           1 :                 continue;
    2210             :             }
    2211             : 
    2212             :             const std::string osRelationName = GenerateNameForRelationship(
    2213          92 :                 pszBaseTableName, pszRelatedTableName, pszRelationName);
    2214             : 
    2215          92 :             std::string osType{};
    2216             :             // defined requirement classes -- for these types the relation name
    2217             :             // will be specific string value from the related tables extension.
    2218             :             // In this case we need to construct a unique relationship name
    2219             :             // based on the related tables
    2220          46 :             if (EQUAL(pszRelationName, "media") ||
    2221          34 :                 EQUAL(pszRelationName, "simple_attributes") ||
    2222          34 :                 EQUAL(pszRelationName, "features") ||
    2223          12 :                 EQUAL(pszRelationName, "attributes") ||
    2224           2 :                 EQUAL(pszRelationName, "tiles"))
    2225             :             {
    2226          44 :                 osType = pszRelationName;
    2227             :             }
    2228             :             else
    2229             :             {
    2230             :                 // user defined types default to features
    2231           2 :                 osType = "features";
    2232             :             }
    2233             : 
    2234             :             std::unique_ptr<GDALRelationship> poRelationship(
    2235             :                 new GDALRelationship(osRelationName, pszBaseTableName,
    2236         138 :                                      pszRelatedTableName, GRC_MANY_TO_MANY));
    2237             : 
    2238          92 :             poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
    2239          92 :             poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
    2240          92 :             poRelationship->SetLeftMappingTableFields({"base_id"});
    2241          92 :             poRelationship->SetRightMappingTableFields({"related_id"});
    2242          46 :             poRelationship->SetMappingTableName(pszMappingTableName);
    2243          46 :             poRelationship->SetRelatedTableType(osType);
    2244             : 
    2245          46 :             m_osMapRelationships[osRelationName] = std::move(poRelationship);
    2246             :         }
    2247             :     }
    2248          30 : }
    2249             : 
    2250             : /************************************************************************/
    2251             : /*                GenerateNameForRelationship()                         */
    2252             : /************************************************************************/
    2253             : 
    2254          66 : std::string GDALGeoPackageDataset::GenerateNameForRelationship(
    2255             :     const char *pszBaseTableName, const char *pszRelatedTableName,
    2256             :     const char *pszType)
    2257             : {
    2258             :     // defined requirement classes -- for these types the relation name will be
    2259             :     // specific string value from the related tables extension. In this case we
    2260             :     // need to construct a unique relationship name based on the related tables
    2261          66 :     if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
    2262          43 :         EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
    2263           8 :         EQUAL(pszType, "tiles"))
    2264             :     {
    2265         116 :         std::ostringstream stream;
    2266             :         stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
    2267          58 :                << pszType;
    2268          58 :         return stream.str();
    2269             :     }
    2270             :     else
    2271             :     {
    2272             :         // user defined types default to features
    2273           8 :         return pszType;
    2274             :     }
    2275             : }
    2276             : 
    2277             : /************************************************************************/
    2278             : /*                       ValidateRelationship()                         */
    2279             : /************************************************************************/
    2280             : 
    2281          24 : bool GDALGeoPackageDataset::ValidateRelationship(
    2282             :     const GDALRelationship *poRelationship, std::string &failureReason)
    2283             : {
    2284             : 
    2285          24 :     if (poRelationship->GetCardinality() !=
    2286             :         GDALRelationshipCardinality::GRC_MANY_TO_MANY)
    2287             :     {
    2288           3 :         failureReason = "Only many to many relationships are supported";
    2289           3 :         return false;
    2290             :     }
    2291             : 
    2292          42 :     std::string osRelatedTableType = poRelationship->GetRelatedTableType();
    2293          53 :     if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
    2294          22 :         osRelatedTableType != "media" &&
    2295          12 :         osRelatedTableType != "simple_attributes" &&
    2296          43 :         osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
    2297             :     {
    2298             :         failureReason =
    2299           4 :             ("Related table type " + osRelatedTableType +
    2300             :              " is not a valid value for the GeoPackage specification. "
    2301             :              "Valid values are: features, media, simple_attributes, "
    2302             :              "attributes, tiles.")
    2303           2 :                 .c_str();
    2304           2 :         return false;
    2305             :     }
    2306             : 
    2307          19 :     const std::string &osLeftTableName = poRelationship->GetLeftTableName();
    2308          19 :     OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2309          19 :         GetLayerByName(osLeftTableName.c_str()));
    2310          19 :     if (!poLeftTable)
    2311             :     {
    2312           2 :         failureReason = ("Left table " + osLeftTableName +
    2313             :                          " is not an existing layer in the dataset")
    2314           1 :                             .c_str();
    2315           1 :         return false;
    2316             :     }
    2317          18 :     const std::string &osRightTableName = poRelationship->GetRightTableName();
    2318          18 :     OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
    2319          18 :         GetLayerByName(osRightTableName.c_str()));
    2320          18 :     if (!poRightTable)
    2321             :     {
    2322           2 :         failureReason = ("Right table " + osRightTableName +
    2323             :                          " is not an existing layer in the dataset")
    2324           1 :                             .c_str();
    2325           1 :         return false;
    2326             :     }
    2327             : 
    2328          17 :     const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
    2329          17 :     if (aosLeftTableFields.empty())
    2330             :     {
    2331           1 :         failureReason = "No left table fields were specified";
    2332           1 :         return false;
    2333             :     }
    2334          16 :     else if (aosLeftTableFields.size() > 1)
    2335             :     {
    2336             :         failureReason = "Only a single left table field is permitted for the "
    2337           1 :                         "GeoPackage specification";
    2338           1 :         return false;
    2339             :     }
    2340             :     else
    2341             :     {
    2342             :         // validate left field exists
    2343          30 :         if (poLeftTable->GetLayerDefn()->GetFieldIndex(
    2344          33 :                 aosLeftTableFields[0].c_str()) < 0 &&
    2345           3 :             !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
    2346             :         {
    2347           2 :             failureReason = ("Left table field " + aosLeftTableFields[0] +
    2348           2 :                              " does not exist in " + osLeftTableName)
    2349           1 :                                 .c_str();
    2350           1 :             return false;
    2351             :         }
    2352             :     }
    2353             : 
    2354          14 :     const auto &aosRightTableFields = poRelationship->GetRightTableFields();
    2355          14 :     if (aosRightTableFields.empty())
    2356             :     {
    2357           1 :         failureReason = "No right table fields were specified";
    2358           1 :         return false;
    2359             :     }
    2360          13 :     else if (aosRightTableFields.size() > 1)
    2361             :     {
    2362             :         failureReason = "Only a single right table field is permitted for the "
    2363           1 :                         "GeoPackage specification";
    2364           1 :         return false;
    2365             :     }
    2366             :     else
    2367             :     {
    2368             :         // validate right field exists
    2369          24 :         if (poRightTable->GetLayerDefn()->GetFieldIndex(
    2370          28 :                 aosRightTableFields[0].c_str()) < 0 &&
    2371           4 :             !EQUAL(poRightTable->GetFIDColumn(),
    2372             :                    aosRightTableFields[0].c_str()))
    2373             :         {
    2374           4 :             failureReason = ("Right table field " + aosRightTableFields[0] +
    2375           4 :                              " does not exist in " + osRightTableName)
    2376           2 :                                 .c_str();
    2377           2 :             return false;
    2378             :         }
    2379             :     }
    2380             : 
    2381          10 :     return true;
    2382             : }
    2383             : 
    2384             : /************************************************************************/
    2385             : /*                         InitRaster()                                 */
    2386             : /************************************************************************/
    2387             : 
    2388         341 : bool GDALGeoPackageDataset::InitRaster(
    2389             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
    2390             :     double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2391             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2392             :     const char *pszContentsMaxY, char **papszOpenOptionsIn,
    2393             :     const SQLResult &oResult, int nIdxInResult)
    2394             : {
    2395         341 :     m_osRasterTable = pszTableName;
    2396         341 :     m_dfTMSMinX = dfMinX;
    2397         341 :     m_dfTMSMaxY = dfMaxY;
    2398             : 
    2399             :     // Despite prior checking, the type might be Binary and
    2400             :     // SQLResultGetValue() not working properly on it
    2401         341 :     int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
    2402         341 :     if (nZoomLevel < 0 || nZoomLevel > 65536)
    2403             :     {
    2404           0 :         return false;
    2405             :     }
    2406         341 :     double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
    2407         341 :     double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
    2408         341 :     if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
    2409             :     {
    2410           0 :         return false;
    2411             :     }
    2412         341 :     int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
    2413         341 :     int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
    2414         341 :     if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
    2415             :         nTileHeight > 65536)
    2416             :     {
    2417           0 :         return false;
    2418             :     }
    2419             :     int nTileMatrixWidth = static_cast<int>(
    2420         682 :         std::min(static_cast<GIntBig>(INT_MAX),
    2421         341 :                  CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
    2422             :     int nTileMatrixHeight = static_cast<int>(
    2423         682 :         std::min(static_cast<GIntBig>(INT_MAX),
    2424         341 :                  CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
    2425         341 :     if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
    2426             :     {
    2427           0 :         return false;
    2428             :     }
    2429             : 
    2430             :     /* Use content bounds in priority over tile_matrix_set bounds */
    2431         341 :     double dfGDALMinX = dfMinX;
    2432         341 :     double dfGDALMinY = dfMinY;
    2433         341 :     double dfGDALMaxX = dfMaxX;
    2434         341 :     double dfGDALMaxY = dfMaxY;
    2435             :     pszContentsMinX =
    2436         341 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
    2437             :     pszContentsMinY =
    2438         341 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
    2439             :     pszContentsMaxX =
    2440         341 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
    2441             :     pszContentsMaxY =
    2442         341 :         CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
    2443         341 :     if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
    2444         341 :         pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
    2445             :     {
    2446         681 :         if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
    2447         340 :             CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
    2448             :         {
    2449         340 :             dfGDALMinX = CPLAtof(pszContentsMinX);
    2450         340 :             dfGDALMinY = CPLAtof(pszContentsMinY);
    2451         340 :             dfGDALMaxX = CPLAtof(pszContentsMaxX);
    2452         340 :             dfGDALMaxY = CPLAtof(pszContentsMaxY);
    2453             :         }
    2454             :         else
    2455             :         {
    2456           1 :             CPLError(CE_Warning, CPLE_AppDefined,
    2457             :                      "Illegal min_x/min_y/max_x/max_y values for %s in open "
    2458             :                      "options and/or gpkg_contents. Using bounds of "
    2459             :                      "gpkg_tile_matrix_set instead",
    2460             :                      pszTableName);
    2461             :         }
    2462             :     }
    2463         341 :     if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
    2464             :     {
    2465           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2466             :                  "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
    2467           0 :         return false;
    2468             :     }
    2469             : 
    2470         341 :     int nBandCount = 0;
    2471             :     const char *pszBAND_COUNT =
    2472         341 :         CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
    2473         341 :     if (poParentDS)
    2474             :     {
    2475          85 :         nBandCount = poParentDS->GetRasterCount();
    2476             :     }
    2477         256 :     else if (m_eDT != GDT_Byte)
    2478             :     {
    2479          63 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
    2480           0 :             !EQUAL(pszBAND_COUNT, "1"))
    2481             :         {
    2482           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    2483             :                      "BAND_COUNT ignored for non-Byte data");
    2484             :         }
    2485          63 :         nBandCount = 1;
    2486             :     }
    2487             :     else
    2488             :     {
    2489         193 :         if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
    2490             :         {
    2491          69 :             nBandCount = atoi(pszBAND_COUNT);
    2492          69 :             if (nBandCount == 1)
    2493           5 :                 GetMetadata("IMAGE_STRUCTURE");
    2494             :         }
    2495             :         else
    2496             :         {
    2497         124 :             GetMetadata("IMAGE_STRUCTURE");
    2498         124 :             nBandCount = m_nBandCountFromMetadata;
    2499         124 :             if (nBandCount == 1)
    2500          30 :                 m_eTF = GPKG_TF_PNG;
    2501             :         }
    2502         193 :         if (nBandCount == 1 && !m_osTFFromMetadata.empty())
    2503             :         {
    2504           2 :             m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
    2505             :         }
    2506         193 :         if (nBandCount <= 0 || nBandCount > 4)
    2507          82 :             nBandCount = 4;
    2508             :     }
    2509             : 
    2510         341 :     return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
    2511             :                       dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
    2512             :                       nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
    2513         341 :                       dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    2514             : }
    2515             : 
    2516             : /************************************************************************/
    2517             : /*                      ComputeTileAndPixelShifts()                     */
    2518             : /************************************************************************/
    2519             : 
    2520         737 : bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
    2521             : {
    2522             :     int nTileWidth, nTileHeight;
    2523         737 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2524             : 
    2525             :     // Compute shift between GDAL origin and TileMatrixSet origin
    2526         737 :     const double dfShiftXPixels =
    2527         737 :         (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
    2528         737 :     if (dfShiftXPixels / nTileWidth <= INT_MIN ||
    2529         735 :         dfShiftXPixels / nTileWidth > INT_MAX)
    2530           2 :         return false;
    2531         735 :     const int64_t nShiftXPixels =
    2532         735 :         static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
    2533         735 :     m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
    2534         735 :     if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
    2535          11 :         m_nShiftXTiles--;
    2536         735 :     m_nShiftXPixelsMod =
    2537         735 :         (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
    2538             :         nTileWidth;
    2539             : 
    2540         735 :     const double dfShiftYPixels =
    2541         735 :         (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
    2542         735 :     if (dfShiftYPixels / nTileHeight <= INT_MIN ||
    2543         735 :         dfShiftYPixels / nTileHeight > INT_MAX)
    2544           1 :         return false;
    2545         734 :     const int64_t nShiftYPixels =
    2546         734 :         static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
    2547         734 :     m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
    2548         734 :     if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
    2549          11 :         m_nShiftYTiles--;
    2550         734 :     m_nShiftYPixelsMod =
    2551         734 :         (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
    2552             :         nTileHeight;
    2553         734 :     return true;
    2554             : }
    2555             : 
    2556             : /************************************************************************/
    2557             : /*                            AllocCachedTiles()                        */
    2558             : /************************************************************************/
    2559             : 
    2560         734 : bool GDALGeoPackageDataset::AllocCachedTiles()
    2561             : {
    2562             :     int nTileWidth, nTileHeight;
    2563         734 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    2564             : 
    2565             :     // We currently need 4 caches because of
    2566             :     // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
    2567         734 :     const int nCacheCount = 4;
    2568             :     /*
    2569             :             (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
    2570             :             (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
    2571             :     */
    2572         734 :     m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
    2573             :         cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
    2574             :                           m_nDTSize),
    2575             :         nTileWidth, nTileHeight));
    2576         734 :     if (m_pabyCachedTiles == nullptr)
    2577             :     {
    2578           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
    2579             :                  nTileWidth, nTileHeight);
    2580           0 :         return false;
    2581             :     }
    2582             : 
    2583         734 :     return true;
    2584             : }
    2585             : 
    2586             : /************************************************************************/
    2587             : /*                         InitRaster()                                 */
    2588             : /************************************************************************/
    2589             : 
    2590         577 : bool GDALGeoPackageDataset::InitRaster(
    2591             :     GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
    2592             :     int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
    2593             :     double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
    2594             :     int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
    2595             :     double dfGDALMaxX, double dfGDALMaxY)
    2596             : {
    2597         577 :     m_osRasterTable = pszTableName;
    2598         577 :     m_dfTMSMinX = dfTMSMinX;
    2599         577 :     m_dfTMSMaxY = dfTMSMaxY;
    2600         577 :     m_nZoomLevel = nZoomLevel;
    2601         577 :     m_nTileMatrixWidth = nTileMatrixWidth;
    2602         577 :     m_nTileMatrixHeight = nTileMatrixHeight;
    2603             : 
    2604         577 :     m_bGeoTransformValid = true;
    2605         577 :     m_adfGeoTransform[0] = dfGDALMinX;
    2606         577 :     m_adfGeoTransform[1] = dfPixelXSize;
    2607         577 :     m_adfGeoTransform[3] = dfGDALMaxY;
    2608         577 :     m_adfGeoTransform[5] = -dfPixelYSize;
    2609         577 :     double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
    2610         577 :     double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
    2611         577 :     if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2612             :     {
    2613           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
    2614             :                  dfRasterXSize, dfRasterYSize);
    2615           0 :         return false;
    2616             :     }
    2617         577 :     nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
    2618         577 :     nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
    2619             : 
    2620         577 :     if (poParentDS)
    2621             :     {
    2622         321 :         m_poParentDS = poParentDS;
    2623         321 :         eAccess = poParentDS->eAccess;
    2624         321 :         hDB = poParentDS->hDB;
    2625         321 :         m_eTF = poParentDS->m_eTF;
    2626         321 :         m_eDT = poParentDS->m_eDT;
    2627         321 :         m_nDTSize = poParentDS->m_nDTSize;
    2628         321 :         m_dfScale = poParentDS->m_dfScale;
    2629         321 :         m_dfOffset = poParentDS->m_dfOffset;
    2630         321 :         m_dfPrecision = poParentDS->m_dfPrecision;
    2631         321 :         m_usGPKGNull = poParentDS->m_usGPKGNull;
    2632         321 :         m_nQuality = poParentDS->m_nQuality;
    2633         321 :         m_nZLevel = poParentDS->m_nZLevel;
    2634         321 :         m_bDither = poParentDS->m_bDither;
    2635             :         /*m_nSRID = poParentDS->m_nSRID;*/
    2636         321 :         m_osWHERE = poParentDS->m_osWHERE;
    2637         321 :         SetDescription(CPLSPrintf("%s - zoom_level=%d",
    2638         321 :                                   poParentDS->GetDescription(), m_nZoomLevel));
    2639             :     }
    2640             : 
    2641        2034 :     for (int i = 1; i <= nBandCount; i++)
    2642             :     {
    2643             :         GDALGeoPackageRasterBand *poNewBand =
    2644        1457 :             new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight);
    2645        1457 :         if (poParentDS)
    2646             :         {
    2647         753 :             int bHasNoData = FALSE;
    2648             :             double dfNoDataValue =
    2649         753 :                 poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    2650         753 :             if (bHasNoData)
    2651          24 :                 poNewBand->SetNoDataValueInternal(dfNoDataValue);
    2652             :         }
    2653        1457 :         SetBand(i, poNewBand);
    2654             : 
    2655        1457 :         if (nBandCount == 1 && m_poCTFromMetadata)
    2656             :         {
    2657           3 :             poNewBand->AssignColorTable(m_poCTFromMetadata.get());
    2658             :         }
    2659        1457 :         if (!m_osNodataValueFromMetadata.empty())
    2660             :         {
    2661           8 :             poNewBand->SetNoDataValueInternal(
    2662             :                 CPLAtof(m_osNodataValueFromMetadata.c_str()));
    2663             :         }
    2664             :     }
    2665             : 
    2666         577 :     if (!ComputeTileAndPixelShifts())
    2667             :     {
    2668           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    2669             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    2670           3 :         return false;
    2671             :     }
    2672             : 
    2673         574 :     GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2674         574 :     GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
    2675             :                                     CPLSPrintf("%d", m_nZoomLevel));
    2676             : 
    2677         574 :     return AllocCachedTiles();
    2678             : }
    2679             : 
    2680             : /************************************************************************/
    2681             : /*                 GDALGPKGMBTilesGetTileFormat()                       */
    2682             : /************************************************************************/
    2683             : 
    2684          79 : GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
    2685             : {
    2686          79 :     GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
    2687          79 :     if (pszTF)
    2688             :     {
    2689          79 :         if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
    2690           1 :             eTF = GPKG_TF_PNG_JPEG;
    2691          78 :         else if (EQUAL(pszTF, "PNG"))
    2692          45 :             eTF = GPKG_TF_PNG;
    2693          33 :         else if (EQUAL(pszTF, "PNG8"))
    2694           6 :             eTF = GPKG_TF_PNG8;
    2695          27 :         else if (EQUAL(pszTF, "JPEG"))
    2696          14 :             eTF = GPKG_TF_JPEG;
    2697          13 :         else if (EQUAL(pszTF, "WEBP"))
    2698          13 :             eTF = GPKG_TF_WEBP;
    2699             :         else
    2700             :         {
    2701           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2702             :                      "Unsuppoted value for TILE_FORMAT: %s", pszTF);
    2703             :         }
    2704             :     }
    2705          79 :     return eTF;
    2706             : }
    2707             : 
    2708          28 : const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
    2709             : {
    2710          28 :     switch (eTF)
    2711             :     {
    2712          26 :         case GPKG_TF_PNG:
    2713             :         case GPKG_TF_PNG8:
    2714          26 :             return "png";
    2715           1 :         case GPKG_TF_JPEG:
    2716           1 :             return "jpg";
    2717           1 :         case GPKG_TF_WEBP:
    2718           1 :             return "webp";
    2719           0 :         default:
    2720           0 :             break;
    2721             :     }
    2722           0 :     CPLError(CE_Failure, CPLE_NotSupported,
    2723             :              "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
    2724           0 :     return nullptr;
    2725             : }
    2726             : 
    2727             : /************************************************************************/
    2728             : /*                         OpenRaster()                                 */
    2729             : /************************************************************************/
    2730             : 
    2731         258 : bool GDALGeoPackageDataset::OpenRaster(
    2732             :     const char *pszTableName, const char *pszIdentifier,
    2733             :     const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
    2734             :     double dfMaxX, double dfMaxY, const char *pszContentsMinX,
    2735             :     const char *pszContentsMinY, const char *pszContentsMaxX,
    2736             :     const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
    2737             : {
    2738         258 :     if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
    2739           0 :         return false;
    2740             : 
    2741             :     // Config option just for debug, and for example force set to NaN
    2742             :     // which is not supported
    2743         516 :     CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
    2744         516 :     CPLString osUom;
    2745         516 :     CPLString osFieldName;
    2746         516 :     CPLString osGridCellEncoding;
    2747         258 :     if (!bIsTiles)
    2748             :     {
    2749          63 :         char *pszSQL = sqlite3_mprintf(
    2750             :             "SELECT datatype, scale, offset, data_null, precision FROM "
    2751             :             "gpkg_2d_gridded_coverage_ancillary "
    2752             :             "WHERE tile_matrix_set_name = '%q' "
    2753             :             "AND datatype IN ('integer', 'float')"
    2754             :             "AND (scale > 0 OR scale IS NULL)",
    2755             :             pszTableName);
    2756          63 :         auto oResult = SQLQuery(hDB, pszSQL);
    2757          63 :         sqlite3_free(pszSQL);
    2758          63 :         if (!oResult || oResult->RowCount() == 0)
    2759             :         {
    2760           0 :             return false;
    2761             :         }
    2762          63 :         const char *pszDataType = oResult->GetValue(0, 0);
    2763          63 :         const char *pszScale = oResult->GetValue(1, 0);
    2764          63 :         const char *pszOffset = oResult->GetValue(2, 0);
    2765          63 :         const char *pszDataNull = oResult->GetValue(3, 0);
    2766          63 :         const char *pszPrecision = oResult->GetValue(4, 0);
    2767          63 :         if (pszDataNull)
    2768          23 :             osDataNull = pszDataNull;
    2769          63 :         if (EQUAL(pszDataType, "float"))
    2770             :         {
    2771           6 :             SetDataType(GDT_Float32);
    2772           6 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    2773             :         }
    2774             :         else
    2775             :         {
    2776          57 :             SetDataType(GDT_Float32);
    2777          57 :             m_eTF = GPKG_TF_PNG_16BIT;
    2778          57 :             const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
    2779          57 :             const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
    2780          57 :             if (dfScale == 1.0)
    2781             :             {
    2782          57 :                 if (dfOffset == 0.0)
    2783             :                 {
    2784          24 :                     SetDataType(GDT_UInt16);
    2785             :                 }
    2786          33 :                 else if (dfOffset == -32768.0)
    2787             :                 {
    2788          33 :                     SetDataType(GDT_Int16);
    2789             :                 }
    2790             :                 // coverity[tainted_data]
    2791           0 :                 else if (dfOffset == -32767.0 && !osDataNull.empty() &&
    2792           0 :                          CPLAtof(osDataNull) == 65535.0)
    2793             :                 // Given that we will map the nodata value to -32768
    2794             :                 {
    2795           0 :                     SetDataType(GDT_Int16);
    2796             :                 }
    2797             :             }
    2798             : 
    2799             :             // Check that the tile offset and scales are compatible of a
    2800             :             // final integer result.
    2801          57 :             if (m_eDT != GDT_Float32)
    2802             :             {
    2803             :                 // coverity[tainted_data]
    2804          57 :                 if (dfScale == 1.0 && dfOffset == -32768.0 &&
    2805         114 :                     !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
    2806             :                 {
    2807             :                     // Given that we will map the nodata value to -32768
    2808           9 :                     pszSQL = sqlite3_mprintf(
    2809             :                         "SELECT 1 FROM "
    2810             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2811             :                         "tpudt_name = '%q' "
    2812             :                         "AND NOT ((offset = 0.0 or offset = 1.0) "
    2813             :                         "AND scale = 1.0) "
    2814             :                         "LIMIT 1",
    2815             :                         pszTableName);
    2816             :                 }
    2817             :                 else
    2818             :                 {
    2819          48 :                     pszSQL = sqlite3_mprintf(
    2820             :                         "SELECT 1 FROM "
    2821             :                         "gpkg_2d_gridded_tile_ancillary WHERE "
    2822             :                         "tpudt_name = '%q' "
    2823             :                         "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
    2824             :                         pszTableName);
    2825             :                 }
    2826          57 :                 sqlite3_stmt *hSQLStmt = nullptr;
    2827             :                 int rc =
    2828          57 :                     sqlite3_prepare_v2(hDB, pszSQL, -1, &hSQLStmt, nullptr);
    2829             : 
    2830          57 :                 if (rc == SQLITE_OK)
    2831             :                 {
    2832          57 :                     if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
    2833             :                     {
    2834           8 :                         SetDataType(GDT_Float32);
    2835             :                     }
    2836          57 :                     sqlite3_finalize(hSQLStmt);
    2837             :                 }
    2838             :                 else
    2839             :                 {
    2840           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2841             :                              "Error when running %s", pszSQL);
    2842             :                 }
    2843          57 :                 sqlite3_free(pszSQL);
    2844             :             }
    2845             : 
    2846          57 :             SetGlobalOffsetScale(dfOffset, dfScale);
    2847             :         }
    2848          63 :         if (pszPrecision)
    2849          63 :             m_dfPrecision = CPLAtof(pszPrecision);
    2850             : 
    2851             :         // Request those columns in a separate query, so as to keep
    2852             :         // compatibility with pre OGC 17-066r1 databases
    2853             :         pszSQL =
    2854          63 :             sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
    2855             :                             "gpkg_2d_gridded_coverage_ancillary "
    2856             :                             "WHERE tile_matrix_set_name = '%q'",
    2857             :                             pszTableName);
    2858          63 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    2859          63 :         oResult = SQLQuery(hDB, pszSQL);
    2860          63 :         CPLPopErrorHandler();
    2861          63 :         sqlite3_free(pszSQL);
    2862          63 :         if (oResult && oResult->RowCount() == 1)
    2863             :         {
    2864          62 :             const char *pszUom = oResult->GetValue(0, 0);
    2865          62 :             if (pszUom)
    2866           2 :                 osUom = pszUom;
    2867          62 :             const char *pszFieldName = oResult->GetValue(1, 0);
    2868          62 :             if (pszFieldName)
    2869          62 :                 osFieldName = pszFieldName;
    2870          62 :             const char *pszGridCellEncoding = oResult->GetValue(2, 0);
    2871          62 :             if (pszGridCellEncoding)
    2872          62 :                 osGridCellEncoding = pszGridCellEncoding;
    2873             :         }
    2874             :     }
    2875             : 
    2876         258 :     m_bRecordInsertedInGPKGContent = true;
    2877         258 :     m_nSRID = nSRSId;
    2878             : 
    2879         258 :     OGRSpatialReference *poSRS = GetSpatialRef(nSRSId);
    2880         258 :     if (poSRS)
    2881             :     {
    2882         257 :         m_oSRS = *poSRS;
    2883         257 :         poSRS->Release();
    2884             :     }
    2885             : 
    2886             :     /* Various sanity checks added in the SELECT */
    2887         258 :     char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
    2888         516 :     CPLString osQuotedTableName(pszQuotedTableName);
    2889         258 :     sqlite3_free(pszQuotedTableName);
    2890         258 :     char *pszSQL = sqlite3_mprintf(
    2891             :         "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
    2892             :         "tile_height, matrix_width, matrix_height "
    2893             :         "FROM gpkg_tile_matrix tm "
    2894             :         "WHERE table_name = %s "
    2895             :         // INT_MAX would be the theoretical maximum value to avoid
    2896             :         // overflows, but that's already a insane value.
    2897             :         "AND zoom_level >= 0 AND zoom_level <= 65536 "
    2898             :         "AND pixel_x_size > 0 AND pixel_y_size > 0 "
    2899             :         "AND tile_width >= 1 AND tile_width <= 65536 "
    2900             :         "AND tile_height >= 1 AND tile_height <= 65536 "
    2901             :         "AND matrix_width >= 1 AND matrix_height >= 1",
    2902             :         osQuotedTableName.c_str());
    2903         516 :     CPLString osSQL(pszSQL);
    2904             :     const char *pszZoomLevel =
    2905         258 :         CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
    2906         258 :     if (pszZoomLevel)
    2907             :     {
    2908           5 :         if (GetUpdate())
    2909           1 :             osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
    2910             :         else
    2911             :         {
    2912             :             osSQL += CPLSPrintf(
    2913             :                 " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
    2914             :                 "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
    2915             :                 atoi(pszZoomLevel), atoi(pszZoomLevel),
    2916           4 :                 osQuotedTableName.c_str());
    2917             :         }
    2918             :     }
    2919             :     // In read-only mode, only lists non empty zoom levels
    2920         253 :     else if (!GetUpdate())
    2921             :     {
    2922             :         osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
    2923             :                             "tm.zoom_level LIMIT 1)",
    2924         204 :                             osQuotedTableName.c_str());
    2925             :     }
    2926             :     else  // if( pszZoomLevel == nullptr )
    2927             :     {
    2928             :         osSQL +=
    2929             :             CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
    2930          49 :                        osQuotedTableName.c_str());
    2931             :     }
    2932         258 :     osSQL += " ORDER BY zoom_level DESC";
    2933             :     // To avoid denial of service.
    2934         258 :     osSQL += " LIMIT 100";
    2935             : 
    2936         516 :     auto oResult = SQLQuery(hDB, osSQL.c_str());
    2937         258 :     if (!oResult || oResult->RowCount() == 0)
    2938             :     {
    2939         100 :         if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
    2940         100 :             pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
    2941             :             pszContentsMaxY != nullptr)
    2942             :         {
    2943          49 :             osSQL = pszSQL;
    2944          49 :             osSQL += " ORDER BY zoom_level DESC";
    2945          49 :             if (!GetUpdate())
    2946          24 :                 osSQL += " LIMIT 1";
    2947          49 :             oResult = SQLQuery(hDB, osSQL.c_str());
    2948             :         }
    2949          50 :         if (!oResult || oResult->RowCount() == 0)
    2950             :         {
    2951           1 :             if (oResult && pszZoomLevel != nullptr)
    2952             :             {
    2953           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2954             :                          "ZOOM_LEVEL is probably not valid w.r.t tile "
    2955             :                          "table content");
    2956             :             }
    2957           1 :             sqlite3_free(pszSQL);
    2958           1 :             return false;
    2959             :         }
    2960             :     }
    2961         257 :     sqlite3_free(pszSQL);
    2962             : 
    2963             :     // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
    2964             :     // actually exist.
    2965             : 
    2966             :     // CAUTION: Do not move those variables inside inner scope !
    2967         514 :     CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
    2968             : 
    2969         257 :     if (CPLTestBool(
    2970             :             CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
    2971             :     {
    2972          13 :         pszSQL = sqlite3_mprintf(
    2973             :             "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
    2974             :             "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
    2975             :             pszTableName, atoi(oResult->GetValue(0, 0)));
    2976          13 :         auto oResult2 = SQLQuery(hDB, pszSQL);
    2977          13 :         sqlite3_free(pszSQL);
    2978          26 :         if (!oResult2 || oResult2->RowCount() == 0 ||
    2979             :             // Can happen if table is empty
    2980          38 :             oResult2->GetValue(0, 0) == nullptr ||
    2981             :             // Can happen if table has no NOT NULL constraint on tile_row
    2982             :             // and that all tile_row are NULL
    2983          12 :             oResult2->GetValue(1, 0) == nullptr)
    2984             :         {
    2985           1 :             return false;
    2986             :         }
    2987          12 :         const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
    2988          12 :         const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
    2989          12 :         const int nTileWidth = atoi(oResult->GetValue(3, 0));
    2990          12 :         const int nTileHeight = atoi(oResult->GetValue(4, 0));
    2991             :         osContentsMinX =
    2992          24 :             CPLSPrintf("%.18g", dfMinX + dfPixelXSize * nTileWidth *
    2993          12 :                                              atoi(oResult2->GetValue(0, 0)));
    2994             :         osContentsMaxY =
    2995          24 :             CPLSPrintf("%.18g", dfMaxY - dfPixelYSize * nTileHeight *
    2996          12 :                                              atoi(oResult2->GetValue(1, 0)));
    2997             :         osContentsMaxX = CPLSPrintf(
    2998          24 :             "%.18g", dfMinX + dfPixelXSize * nTileWidth *
    2999          12 :                                   (1 + atoi(oResult2->GetValue(2, 0))));
    3000             :         osContentsMinY = CPLSPrintf(
    3001          24 :             "%.18g", dfMaxY - dfPixelYSize * nTileHeight *
    3002          12 :                                   (1 + atoi(oResult2->GetValue(3, 0))));
    3003          12 :         pszContentsMinX = osContentsMinX.c_str();
    3004          12 :         pszContentsMinY = osContentsMinY.c_str();
    3005          12 :         pszContentsMaxX = osContentsMaxX.c_str();
    3006          12 :         pszContentsMaxY = osContentsMaxY.c_str();
    3007             :     }
    3008             : 
    3009         256 :     if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
    3010             :                     pszContentsMinX, pszContentsMinY, pszContentsMaxX,
    3011         256 :                     pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
    3012             :     {
    3013           3 :         return false;
    3014             :     }
    3015             : 
    3016             :     auto poBand =
    3017         253 :         reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
    3018         253 :     if (!osDataNull.empty())
    3019             :     {
    3020          23 :         double dfGPKGNoDataValue = CPLAtof(osDataNull);
    3021          23 :         if (m_eTF == GPKG_TF_PNG_16BIT)
    3022             :         {
    3023          21 :             if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
    3024          21 :                 static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
    3025             :             {
    3026           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    3027             :                          "data_null = %.18g is invalid for integer data_type",
    3028             :                          dfGPKGNoDataValue);
    3029             :             }
    3030             :             else
    3031             :             {
    3032          21 :                 m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
    3033          21 :                 if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
    3034           9 :                     dfGPKGNoDataValue = -32768.0;
    3035          12 :                 else if (m_eDT == GDT_Float32)
    3036             :                 {
    3037             :                     // Pick a value that is unlikely to be hit with offset &
    3038             :                     // scale
    3039           4 :                     dfGPKGNoDataValue = -std::numeric_limits<float>::max();
    3040             :                 }
    3041          21 :                 poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
    3042             :             }
    3043             :         }
    3044             :         else
    3045             :         {
    3046           2 :             poBand->SetNoDataValueInternal(
    3047           2 :                 static_cast<float>(dfGPKGNoDataValue));
    3048             :         }
    3049             :     }
    3050         253 :     if (!osUom.empty())
    3051             :     {
    3052           2 :         poBand->SetUnitTypeInternal(osUom);
    3053             :     }
    3054         253 :     if (!osFieldName.empty())
    3055             :     {
    3056          62 :         GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
    3057             :     }
    3058         253 :     if (!osGridCellEncoding.empty())
    3059             :     {
    3060          62 :         if (osGridCellEncoding == "grid-value-is-center")
    3061             :         {
    3062          15 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3063             :                                             GDALMD_AOP_POINT);
    3064             :         }
    3065          47 :         else if (osGridCellEncoding == "grid-value-is-area")
    3066             :         {
    3067          43 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3068             :                                             GDALMD_AOP_AREA);
    3069             :         }
    3070             :         else
    3071             :         {
    3072           4 :             GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
    3073             :                                             GDALMD_AOP_POINT);
    3074           4 :             GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
    3075             :                 "GRID_CELL_ENCODING", osGridCellEncoding);
    3076             :         }
    3077             :     }
    3078             : 
    3079         253 :     CheckUnknownExtensions(true);
    3080             : 
    3081             :     // Do this after CheckUnknownExtensions() so that m_eTF is set to
    3082             :     // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
    3083         253 :     const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
    3084         253 :     if (pszTF)
    3085             :     {
    3086           4 :         if (!GetUpdate())
    3087             :         {
    3088           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3089             :                      "TILE_FORMAT open option ignored in read-only mode");
    3090             :         }
    3091           4 :         else if (m_eTF == GPKG_TF_PNG_16BIT ||
    3092           4 :                  m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3093             :         {
    3094           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    3095             :                      "TILE_FORMAT open option ignored on gridded coverages");
    3096             :         }
    3097             :         else
    3098             :         {
    3099           4 :             GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    3100           4 :             if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
    3101             :             {
    3102           1 :                 if (!RegisterWebPExtension())
    3103           0 :                     return false;
    3104             :             }
    3105           4 :             m_eTF = eTF;
    3106             :         }
    3107             :     }
    3108             : 
    3109         253 :     ParseCompressionOptions(papszOpenOptionsIn);
    3110             : 
    3111         253 :     m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
    3112             : 
    3113             :     // Set metadata
    3114         253 :     if (pszIdentifier && pszIdentifier[0])
    3115         253 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
    3116         253 :     if (pszDescription && pszDescription[0])
    3117          21 :         GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
    3118             : 
    3119             :     // Add overviews
    3120         337 :     for (int i = 1; i < oResult->RowCount(); i++)
    3121             :     {
    3122          85 :         GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3123          85 :         poOvrDS->ShareLockWithParentDataset(this);
    3124          85 :         if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
    3125             :                                  dfMaxY, pszContentsMinX, pszContentsMinY,
    3126             :                                  pszContentsMaxX, pszContentsMaxY,
    3127          85 :                                  papszOpenOptionsIn, *oResult, i))
    3128             :         {
    3129           0 :             delete poOvrDS;
    3130           1 :             break;
    3131             :         }
    3132             : 
    3133          85 :         m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3134         170 :             CPLRealloc(m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
    3135          85 :                                              (m_nOverviewCount + 1)));
    3136          85 :         m_papoOverviewDS[m_nOverviewCount++] = poOvrDS;
    3137             : 
    3138             :         int nTileWidth, nTileHeight;
    3139          85 :         poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3140          86 :         if (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
    3141           1 :             poOvrDS->GetRasterYSize() < nTileHeight)
    3142             :         {
    3143           1 :             break;
    3144             :         }
    3145             :     }
    3146             : 
    3147         253 :     return true;
    3148             : }
    3149             : 
    3150             : /************************************************************************/
    3151             : /*                           GetSpatialRef()                            */
    3152             : /************************************************************************/
    3153             : 
    3154          13 : const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
    3155             : {
    3156          13 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3157             : }
    3158             : 
    3159             : /************************************************************************/
    3160             : /*                           SetSpatialRef()                            */
    3161             : /************************************************************************/
    3162             : 
    3163         136 : CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3164             : {
    3165         136 :     if (nBands == 0)
    3166             :     {
    3167           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3168             :                  "SetProjection() not supported on a dataset with 0 band");
    3169           1 :         return CE_Failure;
    3170             :     }
    3171         135 :     if (eAccess != GA_Update)
    3172             :     {
    3173           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3174             :                  "SetProjection() not supported on read-only dataset");
    3175           1 :         return CE_Failure;
    3176             :     }
    3177             : 
    3178         134 :     const int nSRID = GetSrsId(poSRS);
    3179         268 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3180         134 :     if (poTS && nSRID != poTS->nEPSGCode)
    3181             :     {
    3182           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    3183             :                  "Projection should be EPSG:%d for %s tiling scheme",
    3184           1 :                  poTS->nEPSGCode, m_osTilingScheme.c_str());
    3185           1 :         return CE_Failure;
    3186             :     }
    3187             : 
    3188         133 :     m_nSRID = nSRID;
    3189         133 :     m_oSRS.Clear();
    3190         133 :     if (poSRS)
    3191         132 :         m_oSRS = *poSRS;
    3192             : 
    3193         133 :     if (m_bRecordInsertedInGPKGContent)
    3194             :     {
    3195         108 :         char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
    3196             :                                        "WHERE lower(table_name) = lower('%q')",
    3197             :                                        m_nSRID, m_osRasterTable.c_str());
    3198         108 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3199         108 :         sqlite3_free(pszSQL);
    3200         108 :         if (eErr != OGRERR_NONE)
    3201           0 :             return CE_Failure;
    3202             : 
    3203         108 :         pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
    3204             :                                  "WHERE lower(table_name) = lower('%q')",
    3205             :                                  m_nSRID, m_osRasterTable.c_str());
    3206         108 :         eErr = SQLCommand(hDB, pszSQL);
    3207         108 :         sqlite3_free(pszSQL);
    3208         108 :         if (eErr != OGRERR_NONE)
    3209           0 :             return CE_Failure;
    3210             :     }
    3211             : 
    3212         133 :     return CE_None;
    3213             : }
    3214             : 
    3215             : /************************************************************************/
    3216             : /*                          GetGeoTransform()                           */
    3217             : /************************************************************************/
    3218             : 
    3219          30 : CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
    3220             : {
    3221          30 :     memcpy(padfGeoTransform, m_adfGeoTransform, 6 * sizeof(double));
    3222          30 :     if (!m_bGeoTransformValid)
    3223           1 :         return CE_Failure;
    3224             :     else
    3225          29 :         return CE_None;
    3226             : }
    3227             : 
    3228             : /************************************************************************/
    3229             : /*                          SetGeoTransform()                           */
    3230             : /************************************************************************/
    3231             : 
    3232         164 : CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
    3233             : {
    3234         164 :     if (nBands == 0)
    3235             :     {
    3236           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3237             :                  "SetGeoTransform() not supported on a dataset with 0 band");
    3238           1 :         return CE_Failure;
    3239             :     }
    3240         163 :     if (eAccess != GA_Update)
    3241             :     {
    3242           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3243             :                  "SetGeoTransform() not supported on read-only dataset");
    3244           1 :         return CE_Failure;
    3245             :     }
    3246         162 :     if (m_bGeoTransformValid)
    3247             :     {
    3248           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3249             :                  "Cannot modify geotransform once set");
    3250           1 :         return CE_Failure;
    3251             :     }
    3252         161 :     if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
    3253         161 :         padfGeoTransform[5] > 0.0)
    3254             :     {
    3255           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3256             :                  "Only north-up non rotated geotransform supported");
    3257           0 :         return CE_Failure;
    3258             :     }
    3259             : 
    3260         161 :     if (m_nZoomLevel < 0)
    3261             :     {
    3262         160 :         const auto poTS = GetTilingScheme(m_osTilingScheme);
    3263         160 :         if (poTS)
    3264             :         {
    3265          20 :             double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3266          20 :             double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3267         199 :             for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
    3268         179 :                  m_nZoomLevel++)
    3269             :             {
    3270         198 :                 double dfExpectedPixelXSize =
    3271         198 :                     dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
    3272         198 :                 double dfExpectedPixelYSize =
    3273         198 :                     dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
    3274         198 :                 if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
    3275         198 :                         1e-8 * dfExpectedPixelXSize &&
    3276          19 :                     fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
    3277          19 :                         1e-8 * dfExpectedPixelYSize)
    3278             :                 {
    3279          19 :                     break;
    3280             :                 }
    3281             :             }
    3282          20 :             if (m_nZoomLevel == MAX_ZOOM_LEVEL)
    3283             :             {
    3284           1 :                 m_nZoomLevel = -1;
    3285           1 :                 CPLError(
    3286             :                     CE_Failure, CPLE_NotSupported,
    3287             :                     "Could not find an appropriate zoom level of %s tiling "
    3288             :                     "scheme that matches raster pixel size",
    3289             :                     m_osTilingScheme.c_str());
    3290           1 :                 return CE_Failure;
    3291             :             }
    3292             :         }
    3293             :     }
    3294             : 
    3295         160 :     memcpy(m_adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
    3296         160 :     m_bGeoTransformValid = true;
    3297             : 
    3298         160 :     return FinalizeRasterRegistration();
    3299             : }
    3300             : 
    3301             : /************************************************************************/
    3302             : /*                      FinalizeRasterRegistration()                    */
    3303             : /************************************************************************/
    3304             : 
    3305         160 : CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
    3306             : {
    3307             :     OGRErr eErr;
    3308             : 
    3309         160 :     m_dfTMSMinX = m_adfGeoTransform[0];
    3310         160 :     m_dfTMSMaxY = m_adfGeoTransform[3];
    3311             : 
    3312             :     int nTileWidth, nTileHeight;
    3313         160 :     GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3314             : 
    3315         160 :     if (m_nZoomLevel < 0)
    3316             :     {
    3317         140 :         m_nZoomLevel = 0;
    3318         211 :         while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
    3319         140 :                (nRasterYSize >> m_nZoomLevel) > nTileHeight)
    3320          71 :             m_nZoomLevel++;
    3321             :     }
    3322             : 
    3323         160 :     double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
    3324         160 :     double dfPixelYSizeZoomLevel0 =
    3325         160 :         fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
    3326             :     int nTileXCountZoomLevel0 =
    3327         160 :         std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
    3328             :     int nTileYCountZoomLevel0 =
    3329         160 :         std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
    3330             : 
    3331         320 :     const auto poTS = GetTilingScheme(m_osTilingScheme);
    3332         160 :     if (poTS)
    3333             :     {
    3334          20 :         CPLAssert(m_nZoomLevel >= 0);
    3335          20 :         m_dfTMSMinX = poTS->dfMinX;
    3336          20 :         m_dfTMSMaxY = poTS->dfMaxY;
    3337          20 :         dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
    3338          20 :         dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
    3339          20 :         nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
    3340          20 :         nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
    3341             :     }
    3342         160 :     m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
    3343         160 :     m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
    3344             : 
    3345         160 :     if (!ComputeTileAndPixelShifts())
    3346             :     {
    3347           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3348             :                  "Overflow occurred in ComputeTileAndPixelShifts()");
    3349           0 :         return CE_Failure;
    3350             :     }
    3351             : 
    3352         160 :     if (!AllocCachedTiles())
    3353             :     {
    3354           0 :         return CE_Failure;
    3355             :     }
    3356             : 
    3357         160 :     double dfGDALMinX = m_adfGeoTransform[0];
    3358         160 :     double dfGDALMinY =
    3359         160 :         m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3360         160 :     double dfGDALMaxX =
    3361         160 :         m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3362         160 :     double dfGDALMaxY = m_adfGeoTransform[3];
    3363             : 
    3364         160 :     if (SoftStartTransaction() != OGRERR_NONE)
    3365           0 :         return CE_Failure;
    3366             : 
    3367             :     const char *pszCurrentDate =
    3368         160 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3369             :     CPLString osInsertGpkgContentsFormatting(
    3370             :         "INSERT INTO gpkg_contents "
    3371             :         "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
    3372             :         "last_change,srs_id) VALUES "
    3373         320 :         "('%q','%q','%q','%q',%.18g,%.18g,%.18g,%.18g,");
    3374         160 :     osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
    3375         160 :     osInsertGpkgContentsFormatting += ",%d)";
    3376         320 :     char *pszSQL = sqlite3_mprintf(
    3377             :         osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
    3378         160 :         (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
    3379             :         m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
    3380             :         dfGDALMaxX, dfGDALMaxY,
    3381             :         pszCurrentDate ? pszCurrentDate
    3382             :                        : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
    3383             :         m_nSRID);
    3384             : 
    3385         160 :     eErr = SQLCommand(hDB, pszSQL);
    3386         160 :     sqlite3_free(pszSQL);
    3387         160 :     if (eErr != OGRERR_NONE)
    3388             :     {
    3389           0 :         SoftRollbackTransaction();
    3390           0 :         return CE_Failure;
    3391             :     }
    3392             : 
    3393         160 :     double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
    3394             :                                          dfPixelXSizeZoomLevel0;
    3395         160 :     double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
    3396             :                                          dfPixelYSizeZoomLevel0;
    3397             : 
    3398             :     pszSQL =
    3399         160 :         sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
    3400             :                         "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
    3401             :                         "('%q',%d,%.18g,%.18g,%.18g,%.18g)",
    3402             :                         m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
    3403             :                         dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
    3404         160 :     eErr = SQLCommand(hDB, pszSQL);
    3405         160 :     sqlite3_free(pszSQL);
    3406         160 :     if (eErr != OGRERR_NONE)
    3407             :     {
    3408           0 :         SoftRollbackTransaction();
    3409           0 :         return CE_Failure;
    3410             :     }
    3411             : 
    3412         160 :     m_papoOverviewDS = static_cast<GDALGeoPackageDataset **>(
    3413         160 :         CPLCalloc(sizeof(GDALGeoPackageDataset *), m_nZoomLevel));
    3414             : 
    3415         552 :     for (int i = 0; i <= m_nZoomLevel; i++)
    3416             :     {
    3417         392 :         double dfPixelXSizeZoomLevel = 0.0;
    3418         392 :         double dfPixelYSizeZoomLevel = 0.0;
    3419         392 :         int nTileMatrixWidth = 0;
    3420         392 :         int nTileMatrixHeight = 0;
    3421         392 :         if (EQUAL(m_osTilingScheme, "CUSTOM"))
    3422             :         {
    3423         211 :             dfPixelXSizeZoomLevel =
    3424         211 :                 m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
    3425         211 :             dfPixelYSizeZoomLevel =
    3426         211 :                 fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
    3427             :         }
    3428             :         else
    3429             :         {
    3430         181 :             dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
    3431         181 :             dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
    3432             :         }
    3433         392 :         nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
    3434         392 :         nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
    3435             : 
    3436         392 :         pszSQL = sqlite3_mprintf(
    3437             :             "INSERT INTO gpkg_tile_matrix "
    3438             :             "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
    3439             :             "height,pixel_x_size,pixel_y_size) VALUES "
    3440             :             "('%q',%d,%d,%d,%d,%d,%.18g,%.18g)",
    3441             :             m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
    3442             :             nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
    3443             :             dfPixelYSizeZoomLevel);
    3444         392 :         eErr = SQLCommand(hDB, pszSQL);
    3445         392 :         sqlite3_free(pszSQL);
    3446         392 :         if (eErr != OGRERR_NONE)
    3447             :         {
    3448           0 :             SoftRollbackTransaction();
    3449           0 :             return CE_Failure;
    3450             :         }
    3451             : 
    3452         392 :         if (i < m_nZoomLevel)
    3453             :         {
    3454         232 :             GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3455         232 :             poOvrDS->ShareLockWithParentDataset(this);
    3456         232 :             poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
    3457             :                                 m_dfTMSMaxY, dfPixelXSizeZoomLevel,
    3458             :                                 dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
    3459             :                                 nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
    3460             :                                 dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
    3461             : 
    3462         232 :             m_papoOverviewDS[m_nZoomLevel - 1 - i] = poOvrDS;
    3463             :         }
    3464             :     }
    3465             : 
    3466         160 :     if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
    3467             :     {
    3468          39 :         eErr = SQLCommand(
    3469             :             hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
    3470          39 :         m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
    3471          39 :         if (eErr != OGRERR_NONE)
    3472             :         {
    3473           0 :             SoftRollbackTransaction();
    3474           0 :             return CE_Failure;
    3475             :         }
    3476             :     }
    3477             : 
    3478         160 :     SoftCommitTransaction();
    3479             : 
    3480         160 :     m_nOverviewCount = m_nZoomLevel;
    3481         160 :     m_bRecordInsertedInGPKGContent = true;
    3482             : 
    3483         160 :     return CE_None;
    3484             : }
    3485             : 
    3486             : /************************************************************************/
    3487             : /*                             FlushCache()                             */
    3488             : /************************************************************************/
    3489             : 
    3490        2151 : CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
    3491             : {
    3492        2151 :     if (m_bInFlushCache)
    3493           0 :         return CE_None;
    3494             : 
    3495        2151 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3496             :     {
    3497        2148 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3498             :     }
    3499             : 
    3500        2151 :     if (m_bRemoveOGREmptyTable)
    3501             :     {
    3502         520 :         m_bRemoveOGREmptyTable = false;
    3503         520 :         RemoveOGREmptyTable();
    3504             :     }
    3505             : 
    3506        2151 :     CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
    3507             : 
    3508        2151 :     FlushMetadata();
    3509             : 
    3510        2151 :     if (eAccess == GA_Update || !m_bMetadataDirty)
    3511             :     {
    3512             :         // Needed again as above IFlushCacheWithErrCode()
    3513             :         // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
    3514             :         // which modifies metadata
    3515        2151 :         SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
    3516             :     }
    3517             : 
    3518        2151 :     return eErr;
    3519             : }
    3520             : 
    3521        4310 : CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
    3522             : 
    3523             : {
    3524        4310 :     if (m_bInFlushCache)
    3525        2092 :         return CE_None;
    3526        2218 :     m_bInFlushCache = true;
    3527        2218 :     if (hDB && eAccess == GA_ReadOnly && bAtClosing)
    3528             :     {
    3529             :         // Clean-up metadata that will go to PAM by removing items that
    3530             :         // are reconstructed.
    3531        1556 :         CPLStringList aosMD;
    3532        1372 :         for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
    3533             :              ++papszIter)
    3534             :         {
    3535         594 :             char *pszKey = nullptr;
    3536         594 :             CPLParseNameValue(*papszIter, &pszKey);
    3537        1188 :             if (pszKey &&
    3538         594 :                 (EQUAL(pszKey, "AREA_OR_POINT") ||
    3539         455 :                  EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
    3540         245 :                  EQUAL(pszKey, "ZOOM_LEVEL") ||
    3541         624 :                  STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
    3542             :             {
    3543             :                 // remove it
    3544             :             }
    3545             :             else
    3546             :             {
    3547          30 :                 aosMD.AddString(*papszIter);
    3548             :             }
    3549         594 :             CPLFree(pszKey);
    3550             :         }
    3551         778 :         oMDMD.SetMetadata(aosMD.List());
    3552         778 :         oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
    3553             : 
    3554        1556 :         GDALPamDataset::FlushCache(bAtClosing);
    3555             :     }
    3556             :     else
    3557             :     {
    3558             :         // Short circuit GDALPamDataset to avoid serialization to .aux.xml
    3559        1440 :         GDALDataset::FlushCache(bAtClosing);
    3560             :     }
    3561             : 
    3562        5688 :     for (int i = 0; i < m_nLayers; i++)
    3563             :     {
    3564        3470 :         m_papoLayers[i]->RunDeferredCreationIfNecessary();
    3565        3470 :         m_papoLayers[i]->CreateSpatialIndexIfNecessary();
    3566             :     }
    3567             : 
    3568             :     // Update raster table last_change column in gpkg_contents if needed
    3569        2218 :     if (m_bHasModifiedTiles)
    3570             :     {
    3571         490 :         for (int i = 1; i <= nBands; ++i)
    3572             :         {
    3573             :             auto poBand =
    3574         330 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    3575         330 :             if (!poBand->HaveStatsMetadataBeenSetInThisSession())
    3576             :             {
    3577         327 :                 poBand->InvalidateStatistics();
    3578         327 :                 if (psPam && psPam->pszPamFilename)
    3579         327 :                     VSIUnlink(psPam->pszPamFilename);
    3580             :             }
    3581             :         }
    3582             : 
    3583         160 :         UpdateGpkgContentsLastChange(m_osRasterTable);
    3584             : 
    3585         160 :         m_bHasModifiedTiles = false;
    3586             :     }
    3587             : 
    3588        2218 :     CPLErr eErr = FlushTiles();
    3589             : 
    3590        2218 :     m_bInFlushCache = false;
    3591        2218 :     return eErr;
    3592             : }
    3593             : 
    3594             : /************************************************************************/
    3595             : /*                       GetCurrentDateEscapedSQL()                      */
    3596             : /************************************************************************/
    3597             : 
    3598        1563 : std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
    3599             : {
    3600             :     const char *pszCurrentDate =
    3601        1563 :         CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
    3602        1563 :     if (pszCurrentDate)
    3603           2 :         return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
    3604        1562 :     return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
    3605             : }
    3606             : 
    3607             : /************************************************************************/
    3608             : /*                    UpdateGpkgContentsLastChange()                    */
    3609             : /************************************************************************/
    3610             : 
    3611             : OGRErr
    3612         678 : GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
    3613             : {
    3614             :     char *pszSQL =
    3615         678 :         sqlite3_mprintf("UPDATE gpkg_contents SET "
    3616             :                         "last_change = %s "
    3617             :                         "WHERE lower(table_name) = lower('%q')",
    3618        1356 :                         GetCurrentDateEscapedSQL().c_str(), pszTableName);
    3619         678 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    3620         678 :     sqlite3_free(pszSQL);
    3621         678 :     return eErr;
    3622             : }
    3623             : 
    3624             : /************************************************************************/
    3625             : /*                          IBuildOverviews()                           */
    3626             : /************************************************************************/
    3627             : 
    3628          20 : CPLErr GDALGeoPackageDataset::IBuildOverviews(
    3629             :     const char *pszResampling, int nOverviews, const int *panOverviewList,
    3630             :     int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
    3631             :     void *pProgressData, CSLConstList papszOptions)
    3632             : {
    3633          20 :     if (GetAccess() != GA_Update)
    3634             :     {
    3635           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3636             :                  "Overview building not supported on a database opened in "
    3637             :                  "read-only mode");
    3638           1 :         return CE_Failure;
    3639             :     }
    3640          19 :     if (m_poParentDS != nullptr)
    3641             :     {
    3642           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    3643             :                  "Overview building not supported on overview dataset");
    3644           1 :         return CE_Failure;
    3645             :     }
    3646             : 
    3647          18 :     if (nOverviews == 0)
    3648             :     {
    3649           5 :         for (int i = 0; i < m_nOverviewCount; i++)
    3650           3 :             m_papoOverviewDS[i]->FlushCache(false);
    3651             : 
    3652           2 :         SoftStartTransaction();
    3653             : 
    3654           2 :         if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
    3655             :         {
    3656           1 :             char *pszSQL = sqlite3_mprintf(
    3657             :                 "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
    3658             :                 "(SELECT y.id FROM \"%w\" x "
    3659             :                 "JOIN gpkg_2d_gridded_tile_ancillary y "
    3660             :                 "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
    3661             :                 "x.zoom_level < %d)",
    3662             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
    3663           1 :             OGRErr eErr = SQLCommand(hDB, pszSQL);
    3664           1 :             sqlite3_free(pszSQL);
    3665           1 :             if (eErr != OGRERR_NONE)
    3666             :             {
    3667           0 :                 SoftRollbackTransaction();
    3668           0 :                 return CE_Failure;
    3669             :             }
    3670             :         }
    3671             : 
    3672             :         char *pszSQL =
    3673           2 :             sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
    3674             :                             m_osRasterTable.c_str(), m_nZoomLevel);
    3675           2 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
    3676           2 :         sqlite3_free(pszSQL);
    3677           2 :         if (eErr != OGRERR_NONE)
    3678             :         {
    3679           0 :             SoftRollbackTransaction();
    3680           0 :             return CE_Failure;
    3681             :         }
    3682             : 
    3683           2 :         SoftCommitTransaction();
    3684             : 
    3685           2 :         return CE_None;
    3686             :     }
    3687             : 
    3688          16 :     if (nBandsIn != nBands)
    3689             :     {
    3690           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3691             :                  "Generation of overviews in GPKG only"
    3692             :                  "supported when operating on all bands.");
    3693           0 :         return CE_Failure;
    3694             :     }
    3695             : 
    3696          16 :     if (m_nOverviewCount == 0)
    3697             :     {
    3698           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3699             :                  "Image too small to support overviews");
    3700           0 :         return CE_Failure;
    3701             :     }
    3702             : 
    3703          16 :     FlushCache(false);
    3704          60 :     for (int i = 0; i < nOverviews; i++)
    3705             :     {
    3706          47 :         if (panOverviewList[i] < 2)
    3707             :         {
    3708           1 :             CPLError(CE_Failure, CPLE_IllegalArg,
    3709             :                      "Overview factor must be >= 2");
    3710           1 :             return CE_Failure;
    3711             :         }
    3712             : 
    3713          46 :         bool bFound = false;
    3714          46 :         int jCandidate = -1;
    3715          46 :         int nMaxOvFactor = 0;
    3716         196 :         for (int j = 0; j < m_nOverviewCount; j++)
    3717             :         {
    3718         190 :             auto poODS = m_papoOverviewDS[j];
    3719         190 :             const int nOvFactor = static_cast<int>(
    3720         190 :                 0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3721             : 
    3722         190 :             nMaxOvFactor = nOvFactor;
    3723             : 
    3724         190 :             if (nOvFactor == panOverviewList[i])
    3725             :             {
    3726          40 :                 bFound = true;
    3727          40 :                 break;
    3728             :             }
    3729             : 
    3730         150 :             if (jCandidate < 0 && nOvFactor > panOverviewList[i])
    3731           1 :                 jCandidate = j;
    3732             :         }
    3733             : 
    3734          46 :         if (!bFound)
    3735             :         {
    3736             :             /* Mostly for debug */
    3737           6 :             if (!CPLTestBool(CPLGetConfigOption(
    3738             :                     "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
    3739             :             {
    3740           2 :                 CPLString osOvrList;
    3741           4 :                 for (int j = 0; j < m_nOverviewCount; j++)
    3742             :                 {
    3743           2 :                     auto poODS = m_papoOverviewDS[j];
    3744           2 :                     const int nOvFactor =
    3745           2 :                         static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
    3746           2 :                                                    m_adfGeoTransform[1]);
    3747             : 
    3748           2 :                     if (j != 0)
    3749           0 :                         osOvrList += " ";
    3750           2 :                     osOvrList += CPLSPrintf("%d", nOvFactor);
    3751             :                 }
    3752           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    3753             :                          "Only overviews %s can be computed",
    3754             :                          osOvrList.c_str());
    3755           2 :                 return CE_Failure;
    3756             :             }
    3757             :             else
    3758             :             {
    3759           4 :                 int nOvFactor = panOverviewList[i];
    3760           4 :                 if (jCandidate < 0)
    3761           3 :                     jCandidate = m_nOverviewCount;
    3762             : 
    3763           4 :                 int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
    3764           4 :                 int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
    3765           4 :                 if (!(jCandidate == m_nOverviewCount &&
    3766           3 :                       nOvFactor == 2 * nMaxOvFactor) &&
    3767           1 :                     !m_bZoomOther)
    3768             :                 {
    3769           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3770             :                              "Use of overview factor %d causes gpkg_zoom_other "
    3771             :                              "extension to be needed",
    3772             :                              nOvFactor);
    3773           1 :                     RegisterZoomOtherExtension();
    3774           1 :                     m_bZoomOther = true;
    3775             :                 }
    3776             : 
    3777           4 :                 SoftStartTransaction();
    3778             : 
    3779           4 :                 CPLAssert(jCandidate > 0);
    3780           4 :                 int nNewZoomLevel =
    3781           4 :                     m_papoOverviewDS[jCandidate - 1]->m_nZoomLevel;
    3782             : 
    3783             :                 char *pszSQL;
    3784             :                 OGRErr eErr;
    3785          24 :                 for (int k = 0; k <= jCandidate; k++)
    3786             :                 {
    3787          60 :                     pszSQL = sqlite3_mprintf(
    3788             :                         "UPDATE gpkg_tile_matrix SET zoom_level = %d "
    3789             :                         "WHERE lower(table_name) = lower('%q') AND zoom_level "
    3790             :                         "= %d",
    3791          20 :                         m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
    3792          20 :                         m_nZoomLevel - k);
    3793          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3794          20 :                     sqlite3_free(pszSQL);
    3795          20 :                     if (eErr != OGRERR_NONE)
    3796             :                     {
    3797           0 :                         SoftRollbackTransaction();
    3798           0 :                         return CE_Failure;
    3799             :                     }
    3800             : 
    3801             :                     pszSQL =
    3802          20 :                         sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
    3803             :                                         "WHERE zoom_level = %d",
    3804             :                                         m_osRasterTable.c_str(),
    3805          20 :                                         m_nZoomLevel - k + 1, m_nZoomLevel - k);
    3806          20 :                     eErr = SQLCommand(hDB, pszSQL);
    3807          20 :                     sqlite3_free(pszSQL);
    3808          20 :                     if (eErr != OGRERR_NONE)
    3809             :                     {
    3810           0 :                         SoftRollbackTransaction();
    3811           0 :                         return CE_Failure;
    3812             :                     }
    3813             :                 }
    3814             : 
    3815           4 :                 double dfGDALMinX = m_adfGeoTransform[0];
    3816           4 :                 double dfGDALMinY =
    3817           4 :                     m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
    3818           4 :                 double dfGDALMaxX =
    3819           4 :                     m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
    3820           4 :                 double dfGDALMaxY = m_adfGeoTransform[3];
    3821           4 :                 double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
    3822           4 :                 double dfPixelYSizeZoomLevel =
    3823           4 :                     fabs(m_adfGeoTransform[5]) * nOvFactor;
    3824             :                 int nTileWidth, nTileHeight;
    3825           4 :                 GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
    3826           4 :                 int nTileMatrixWidth = (nOvXSize + nTileWidth - 1) / nTileWidth;
    3827           4 :                 int nTileMatrixHeight =
    3828           4 :                     (nOvYSize + nTileHeight - 1) / nTileHeight;
    3829           4 :                 pszSQL = sqlite3_mprintf(
    3830             :                     "INSERT INTO gpkg_tile_matrix "
    3831             :                     "(table_name,zoom_level,matrix_width,matrix_height,tile_"
    3832             :                     "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
    3833             :                     "('%q',%d,%d,%d,%d,%d,%.18g,%.18g)",
    3834             :                     m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
    3835             :                     nTileMatrixHeight, nTileWidth, nTileHeight,
    3836             :                     dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
    3837           4 :                 eErr = SQLCommand(hDB, pszSQL);
    3838           4 :                 sqlite3_free(pszSQL);
    3839           4 :                 if (eErr != OGRERR_NONE)
    3840             :                 {
    3841           0 :                     SoftRollbackTransaction();
    3842           0 :                     return CE_Failure;
    3843             :                 }
    3844             : 
    3845           4 :                 SoftCommitTransaction();
    3846             : 
    3847           4 :                 m_nZoomLevel++; /* this change our zoom level as well as
    3848             :                                    previous overviews */
    3849          20 :                 for (int k = 0; k < jCandidate; k++)
    3850          16 :                     m_papoOverviewDS[k]->m_nZoomLevel++;
    3851             : 
    3852           4 :                 GDALGeoPackageDataset *poOvrDS = new GDALGeoPackageDataset();
    3853           4 :                 poOvrDS->ShareLockWithParentDataset(this);
    3854           4 :                 poOvrDS->InitRaster(
    3855             :                     this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
    3856             :                     m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
    3857             :                     nTileWidth, nTileHeight, nTileMatrixWidth,
    3858             :                     nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
    3859             :                     dfGDALMaxY);
    3860           4 :                 m_papoOverviewDS =
    3861           8 :                     static_cast<GDALGeoPackageDataset **>(CPLRealloc(
    3862           4 :                         m_papoOverviewDS, sizeof(GDALGeoPackageDataset *) *
    3863           4 :                                               (m_nOverviewCount + 1)));
    3864             : 
    3865           4 :                 if (jCandidate < m_nOverviewCount)
    3866             :                 {
    3867           1 :                     memmove(m_papoOverviewDS + jCandidate + 1,
    3868           1 :                             m_papoOverviewDS + jCandidate,
    3869             :                             sizeof(GDALGeoPackageDataset *) *
    3870           1 :                                 (m_nOverviewCount - jCandidate));
    3871             :                 }
    3872           4 :                 m_papoOverviewDS[jCandidate] = poOvrDS;
    3873           4 :                 m_nOverviewCount++;
    3874             :             }
    3875             :         }
    3876             :     }
    3877             : 
    3878             :     GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
    3879          13 :         CPLCalloc(sizeof(GDALRasterBand **), nBands));
    3880          13 :     CPLErr eErr = CE_None;
    3881          49 :     for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
    3882             :     {
    3883          72 :         papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
    3884          36 :             CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
    3885          36 :         int iCurOverview = 0;
    3886         185 :         for (int i = 0; i < nOverviews; i++)
    3887             :         {
    3888         149 :             int j = 0;  // Used after for.
    3889         724 :             for (; j < m_nOverviewCount; j++)
    3890             :             {
    3891         724 :                 auto poODS = m_papoOverviewDS[j];
    3892         724 :                 const int nOvFactor = static_cast<int>(
    3893         724 :                     0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
    3894             : 
    3895         724 :                 if (nOvFactor == panOverviewList[i])
    3896             :                 {
    3897         298 :                     papapoOverviewBands[iBand][iCurOverview] =
    3898         149 :                         poODS->GetRasterBand(iBand + 1);
    3899         149 :                     iCurOverview++;
    3900         149 :                     break;
    3901             :                 }
    3902             :             }
    3903         149 :             if (j == m_nOverviewCount)
    3904             :             {
    3905           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3906             :                          "Could not find dataset corresponding to ov factor %d",
    3907           0 :                          panOverviewList[i]);
    3908           0 :                 eErr = CE_Failure;
    3909             :             }
    3910             :         }
    3911          36 :         if (eErr == CE_None)
    3912             :         {
    3913          36 :             CPLAssert(iCurOverview == nOverviews);
    3914             :         }
    3915             :     }
    3916             : 
    3917          13 :     if (eErr == CE_None)
    3918          13 :         eErr = GDALRegenerateOverviewsMultiBand(
    3919          13 :             nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
    3920             :             pfnProgress, pProgressData, papszOptions);
    3921             : 
    3922          49 :     for (int iBand = 0; iBand < nBands; iBand++)
    3923             :     {
    3924          36 :         CPLFree(papapoOverviewBands[iBand]);
    3925             :     }
    3926          13 :     CPLFree(papapoOverviewBands);
    3927             : 
    3928          13 :     return eErr;
    3929             : }
    3930             : 
    3931             : /************************************************************************/
    3932             : /*                            GetFileList()                             */
    3933             : /************************************************************************/
    3934             : 
    3935          36 : char **GDALGeoPackageDataset::GetFileList()
    3936             : {
    3937          36 :     TryLoadXML();
    3938          36 :     return GDALPamDataset::GetFileList();
    3939             : }
    3940             : 
    3941             : /************************************************************************/
    3942             : /*                      GetMetadataDomainList()                         */
    3943             : /************************************************************************/
    3944             : 
    3945          26 : char **GDALGeoPackageDataset::GetMetadataDomainList()
    3946             : {
    3947          26 :     GetMetadata();
    3948          26 :     if (!m_osRasterTable.empty())
    3949           5 :         GetMetadata("GEOPACKAGE");
    3950          26 :     return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
    3951          26 :                                    TRUE, "SUBDATASETS", nullptr);
    3952             : }
    3953             : 
    3954             : /************************************************************************/
    3955             : /*                        CheckMetadataDomain()                         */
    3956             : /************************************************************************/
    3957             : 
    3958        4216 : const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
    3959             : {
    3960        4378 :     if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
    3961         162 :         m_osRasterTable.empty())
    3962             :     {
    3963           4 :         CPLError(
    3964             :             CE_Warning, CPLE_IllegalArg,
    3965             :             "Using GEOPACKAGE for a non-raster geopackage is not supported. "
    3966             :             "Using default domain instead");
    3967           4 :         return nullptr;
    3968             :     }
    3969        4212 :     return pszDomain;
    3970             : }
    3971             : 
    3972             : /************************************************************************/
    3973             : /*                           HasMetadataTables()                        */
    3974             : /************************************************************************/
    3975             : 
    3976        4112 : bool GDALGeoPackageDataset::HasMetadataTables() const
    3977             : {
    3978        4112 :     if (m_nHasMetadataTables < 0)
    3979             :     {
    3980             :         const int nCount =
    3981        1582 :             SQLGetInteger(hDB,
    3982             :                           "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
    3983             :                           "('gpkg_metadata', 'gpkg_metadata_reference') "
    3984             :                           "AND type IN ('table', 'view')",
    3985             :                           nullptr);
    3986        1582 :         m_nHasMetadataTables = nCount == 2;
    3987             :     }
    3988        4112 :     return CPL_TO_BOOL(m_nHasMetadataTables);
    3989             : }
    3990             : 
    3991             : /************************************************************************/
    3992             : /*                         HasDataColumnsTable()                        */
    3993             : /************************************************************************/
    3994             : 
    3995         796 : bool GDALGeoPackageDataset::HasDataColumnsTable() const
    3996             : {
    3997        1592 :     const int nCount = SQLGetInteger(
    3998         796 :         hDB,
    3999             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
    4000             :         "AND type IN ('table', 'view')",
    4001             :         nullptr);
    4002         796 :     return nCount == 1;
    4003             : }
    4004             : 
    4005             : /************************************************************************/
    4006             : /*                    HasDataColumnConstraintsTable()                   */
    4007             : /************************************************************************/
    4008             : 
    4009         119 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
    4010             : {
    4011         119 :     const int nCount = SQLGetInteger(hDB,
    4012             :                                      "SELECT 1 FROM sqlite_master WHERE name = "
    4013             :                                      "'gpkg_data_column_constraints'"
    4014             :                                      "AND type IN ('table', 'view')",
    4015             :                                      nullptr);
    4016         119 :     return nCount == 1;
    4017             : }
    4018             : 
    4019             : /************************************************************************/
    4020             : /*                  HasDataColumnConstraintsTableGPKG_1_0()             */
    4021             : /************************************************************************/
    4022             : 
    4023          73 : bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
    4024             : {
    4025          73 :     if (m_nApplicationId != GP10_APPLICATION_ID)
    4026          71 :         return false;
    4027             :     // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
    4028             :     // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
    4029           2 :     bool bRet = false;
    4030           2 :     sqlite3_stmt *hSQLStmt = nullptr;
    4031           2 :     int rc = sqlite3_prepare_v2(hDB,
    4032             :                                 "SELECT minIsInclusive, maxIsInclusive FROM "
    4033             :                                 "gpkg_data_column_constraints",
    4034             :                                 -1, &hSQLStmt, nullptr);
    4035           2 :     if (rc == SQLITE_OK)
    4036             :     {
    4037           2 :         bRet = true;
    4038           2 :         sqlite3_finalize(hSQLStmt);
    4039             :     }
    4040           2 :     return bRet;
    4041             : }
    4042             : 
    4043             : /************************************************************************/
    4044             : /*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
    4045             : /************************************************************************/
    4046             : 
    4047          49 : bool GDALGeoPackageDataset::
    4048             :     CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
    4049             : {
    4050          49 :     if (!HasDataColumnsTable())
    4051             :     {
    4052             :         // Geopackage < 1.3 had
    4053             :         // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
    4054             :         // gpkg_contents(table_name) instead of the unique constraint.
    4055          10 :         if (OGRERR_NONE !=
    4056          10 :             SQLCommand(
    4057             :                 GetDB(),
    4058             :                 "CREATE TABLE gpkg_data_columns ("
    4059             :                 "table_name TEXT NOT NULL,"
    4060             :                 "column_name TEXT NOT NULL,"
    4061             :                 "name TEXT,"
    4062             :                 "title TEXT,"
    4063             :                 "description TEXT,"
    4064             :                 "mime_type TEXT,"
    4065             :                 "constraint_name TEXT,"
    4066             :                 "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
    4067             :                 "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
    4068             :         {
    4069           0 :             return false;
    4070             :         }
    4071             :     }
    4072          49 :     if (!HasDataColumnConstraintsTable())
    4073             :     {
    4074          22 :         const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    4075          11 :                                            ? "min_is_inclusive"
    4076             :                                            : "minIsInclusive";
    4077          22 :         const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
    4078          11 :                                            ? "max_is_inclusive"
    4079             :                                            : "maxIsInclusive";
    4080             : 
    4081             :         const std::string osSQL(
    4082             :             CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
    4083             :                        "constraint_name TEXT NOT NULL,"
    4084             :                        "constraint_type TEXT NOT NULL,"
    4085             :                        "value TEXT,"
    4086             :                        "min NUMERIC,"
    4087             :                        "%s BOOLEAN,"
    4088             :                        "max NUMERIC,"
    4089             :                        "%s BOOLEAN,"
    4090             :                        "description TEXT,"
    4091             :                        "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
    4092             :                        "constraint_type, value));",
    4093          11 :                        min_is_inclusive, max_is_inclusive));
    4094          11 :         if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
    4095             :         {
    4096           0 :             return false;
    4097             :         }
    4098             :     }
    4099          49 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4100             :     {
    4101           0 :         return false;
    4102             :     }
    4103          49 :     if (SQLGetInteger(GetDB(),
    4104             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4105             :                       "table_name = 'gpkg_data_columns'",
    4106          49 :                       nullptr) != 1)
    4107             :     {
    4108          11 :         if (OGRERR_NONE !=
    4109          11 :             SQLCommand(
    4110             :                 GetDB(),
    4111             :                 "INSERT INTO gpkg_extensions "
    4112             :                 "(table_name,column_name,extension_name,definition,scope) "
    4113             :                 "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
    4114             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4115             :                 "'read-write')"))
    4116             :         {
    4117           0 :             return false;
    4118             :         }
    4119             :     }
    4120          49 :     if (SQLGetInteger(GetDB(),
    4121             :                       "SELECT 1 FROM gpkg_extensions WHERE "
    4122             :                       "table_name = 'gpkg_data_column_constraints'",
    4123          49 :                       nullptr) != 1)
    4124             :     {
    4125          11 :         if (OGRERR_NONE !=
    4126          11 :             SQLCommand(
    4127             :                 GetDB(),
    4128             :                 "INSERT INTO gpkg_extensions "
    4129             :                 "(table_name,column_name,extension_name,definition,scope) "
    4130             :                 "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
    4131             :                 "'http://www.geopackage.org/spec121/#extension_schema', "
    4132             :                 "'read-write')"))
    4133             :         {
    4134           0 :             return false;
    4135             :         }
    4136             :     }
    4137             : 
    4138          49 :     return true;
    4139             : }
    4140             : 
    4141             : /************************************************************************/
    4142             : /*                        HasGpkgextRelationsTable()                    */
    4143             : /************************************************************************/
    4144             : 
    4145         892 : bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
    4146             : {
    4147        1784 :     const int nCount = SQLGetInteger(
    4148         892 :         hDB,
    4149             :         "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
    4150             :         "AND type IN ('table', 'view')",
    4151             :         nullptr);
    4152         892 :     return nCount == 1;
    4153             : }
    4154             : 
    4155             : /************************************************************************/
    4156             : /*                    CreateRelationsTableIfNecessary()                 */
    4157             : /************************************************************************/
    4158             : 
    4159           7 : bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
    4160             : {
    4161           7 :     if (HasGpkgextRelationsTable())
    4162             :     {
    4163           5 :         return true;
    4164             :     }
    4165             : 
    4166           2 :     if (OGRERR_NONE !=
    4167           2 :         SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
    4168             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    4169             :                             "base_table_name TEXT NOT NULL,"
    4170             :                             "base_primary_column TEXT NOT NULL DEFAULT 'id',"
    4171             :                             "related_table_name TEXT NOT NULL,"
    4172             :                             "related_primary_column TEXT NOT NULL DEFAULT 'id',"
    4173             :                             "relation_name TEXT NOT NULL,"
    4174             :                             "mapping_table_name TEXT NOT NULL UNIQUE);"))
    4175             :     {
    4176           0 :         return false;
    4177             :     }
    4178             : 
    4179           2 :     return true;
    4180             : }
    4181             : 
    4182             : /************************************************************************/
    4183             : /*                        HasQGISLayerStyles()                          */
    4184             : /************************************************************************/
    4185             : 
    4186           9 : bool GDALGeoPackageDataset::HasQGISLayerStyles() const
    4187             : {
    4188             :     // QGIS layer_styles extension:
    4189             :     // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
    4190           9 :     bool bRet = false;
    4191             :     const int nCount =
    4192           9 :         SQLGetInteger(hDB,
    4193             :                       "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
    4194             :                       "AND type = 'table'",
    4195             :                       nullptr);
    4196           9 :     if (nCount == 1)
    4197             :     {
    4198           1 :         sqlite3_stmt *hSQLStmt = nullptr;
    4199           2 :         int rc = sqlite3_prepare_v2(
    4200           1 :             hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
    4201             :             &hSQLStmt, nullptr);
    4202           1 :         if (rc == SQLITE_OK)
    4203             :         {
    4204           1 :             bRet = true;
    4205           1 :             sqlite3_finalize(hSQLStmt);
    4206             :         }
    4207             :     }
    4208           9 :     return bRet;
    4209             : }
    4210             : 
    4211             : /************************************************************************/
    4212             : /*                            GetMetadata()                             */
    4213             : /************************************************************************/
    4214             : 
    4215        2832 : char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
    4216             : 
    4217             : {
    4218        2832 :     pszDomain = CheckMetadataDomain(pszDomain);
    4219        2832 :     if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
    4220          44 :         return m_aosSubDatasets.List();
    4221             : 
    4222        2788 :     if (m_bHasReadMetadataFromStorage)
    4223        1230 :         return GDALPamDataset::GetMetadata(pszDomain);
    4224             : 
    4225        1558 :     m_bHasReadMetadataFromStorage = true;
    4226             : 
    4227        1558 :     TryLoadXML();
    4228             : 
    4229        1558 :     if (!HasMetadataTables())
    4230        1170 :         return GDALPamDataset::GetMetadata(pszDomain);
    4231             : 
    4232         388 :     char *pszSQL = nullptr;
    4233         388 :     if (!m_osRasterTable.empty())
    4234             :     {
    4235         156 :         pszSQL = sqlite3_mprintf(
    4236             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4237             :             "mdr.reference_scope FROM gpkg_metadata md "
    4238             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4239             :             "WHERE "
    4240             :             "(mdr.reference_scope = 'geopackage' OR "
    4241             :             "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
    4242             :             "lower('%q'))) ORDER BY md.id "
    4243             :             "LIMIT 1000",  // to avoid denial of service
    4244             :             m_osRasterTable.c_str());
    4245             :     }
    4246             :     else
    4247             :     {
    4248         232 :         pszSQL = sqlite3_mprintf(
    4249             :             "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
    4250             :             "mdr.reference_scope FROM gpkg_metadata md "
    4251             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4252             :             "WHERE "
    4253             :             "mdr.reference_scope = 'geopackage' ORDER BY md.id "
    4254             :             "LIMIT 1000"  // to avoid denial of service
    4255             :         );
    4256             :     }
    4257             : 
    4258         776 :     auto oResult = SQLQuery(hDB, pszSQL);
    4259         388 :     sqlite3_free(pszSQL);
    4260         388 :     if (!oResult)
    4261             :     {
    4262           0 :         return GDALPamDataset::GetMetadata(pszDomain);
    4263             :     }
    4264             : 
    4265         388 :     char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
    4266             : 
    4267             :     /* GDAL metadata */
    4268         565 :     for (int i = 0; i < oResult->RowCount(); i++)
    4269             :     {
    4270         177 :         const char *pszMetadata = oResult->GetValue(0, i);
    4271         177 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4272         177 :         const char *pszMimeType = oResult->GetValue(2, i);
    4273         177 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4274         177 :         if (pszMetadata && pszMDStandardURI && pszMimeType &&
    4275         177 :             pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4276         161 :             EQUAL(pszMimeType, "text/xml"))
    4277             :         {
    4278         161 :             CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
    4279         161 :             if (psXMLNode)
    4280             :             {
    4281         322 :                 GDALMultiDomainMetadata oLocalMDMD;
    4282         161 :                 oLocalMDMD.XMLInit(psXMLNode, FALSE);
    4283         307 :                 if (!m_osRasterTable.empty() &&
    4284         146 :                     EQUAL(pszReferenceScope, "geopackage"))
    4285             :                 {
    4286           6 :                     oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
    4287             :                 }
    4288             :                 else
    4289             :                 {
    4290             :                     papszMetadata =
    4291         155 :                         CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
    4292         155 :                     CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
    4293         155 :                     CSLConstList papszIter = papszDomainList;
    4294         407 :                     while (papszIter && *papszIter)
    4295             :                     {
    4296         252 :                         if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
    4297             :                         {
    4298             :                             CSLConstList papszMD =
    4299         113 :                                 oLocalMDMD.GetMetadata(*papszIter);
    4300             :                             const char *pszBAND_COUNT =
    4301         113 :                                 CSLFetchNameValue(papszMD, "BAND_COUNT");
    4302         113 :                             if (pszBAND_COUNT)
    4303         111 :                                 m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
    4304             : 
    4305             :                             const char *pszCOLOR_TABLE =
    4306         113 :                                 CSLFetchNameValue(papszMD, "COLOR_TABLE");
    4307         113 :                             if (pszCOLOR_TABLE)
    4308             :                             {
    4309             :                                 const CPLStringList aosTokens(
    4310             :                                     CSLTokenizeString2(pszCOLOR_TABLE, "{,",
    4311          26 :                                                        0));
    4312          13 :                                 if ((aosTokens.size() % 4) == 0)
    4313             :                                 {
    4314          13 :                                     const int nColors = aosTokens.size() / 4;
    4315             :                                     m_poCTFromMetadata =
    4316          13 :                                         std::make_unique<GDALColorTable>();
    4317        3341 :                                     for (int iColor = 0; iColor < nColors;
    4318             :                                          ++iColor)
    4319             :                                     {
    4320             :                                         GDALColorEntry sEntry;
    4321        3328 :                                         sEntry.c1 = static_cast<short>(
    4322        3328 :                                             atoi(aosTokens[4 * iColor + 0]));
    4323        3328 :                                         sEntry.c2 = static_cast<short>(
    4324        3328 :                                             atoi(aosTokens[4 * iColor + 1]));
    4325        3328 :                                         sEntry.c3 = static_cast<short>(
    4326        3328 :                                             atoi(aosTokens[4 * iColor + 2]));
    4327        3328 :                                         sEntry.c4 = static_cast<short>(
    4328        3328 :                                             atoi(aosTokens[4 * iColor + 3]));
    4329        3328 :                                         m_poCTFromMetadata->SetColorEntry(
    4330             :                                             iColor, &sEntry);
    4331             :                                     }
    4332             :                                 }
    4333             :                             }
    4334             : 
    4335             :                             const char *pszTILE_FORMAT =
    4336         113 :                                 CSLFetchNameValue(papszMD, "TILE_FORMAT");
    4337         113 :                             if (pszTILE_FORMAT)
    4338             :                             {
    4339           8 :                                 m_osTFFromMetadata = pszTILE_FORMAT;
    4340           8 :                                 oMDMD.SetMetadataItem("TILE_FORMAT",
    4341             :                                                       pszTILE_FORMAT,
    4342             :                                                       "IMAGE_STRUCTURE");
    4343             :                             }
    4344             : 
    4345             :                             const char *pszNodataValue =
    4346         113 :                                 CSLFetchNameValue(papszMD, "NODATA_VALUE");
    4347         113 :                             if (pszNodataValue)
    4348             :                             {
    4349           2 :                                 m_osNodataValueFromMetadata = pszNodataValue;
    4350             :                             }
    4351             :                         }
    4352             : 
    4353         139 :                         else if (!EQUAL(*papszIter, "") &&
    4354          12 :                                  !STARTS_WITH(*papszIter, "BAND_"))
    4355             :                         {
    4356          12 :                             oMDMD.SetMetadata(
    4357           6 :                                 oLocalMDMD.GetMetadata(*papszIter), *papszIter);
    4358             :                         }
    4359         252 :                         papszIter++;
    4360             :                     }
    4361             :                 }
    4362         161 :                 CPLDestroyXMLNode(psXMLNode);
    4363             :             }
    4364             :         }
    4365             :     }
    4366             : 
    4367         388 :     GDALPamDataset::SetMetadata(papszMetadata);
    4368         388 :     CSLDestroy(papszMetadata);
    4369         388 :     papszMetadata = nullptr;
    4370             : 
    4371             :     /* Add non-GDAL metadata now */
    4372         388 :     int nNonGDALMDILocal = 1;
    4373         388 :     int nNonGDALMDIGeopackage = 1;
    4374         565 :     for (int i = 0; i < oResult->RowCount(); i++)
    4375             :     {
    4376         177 :         const char *pszMetadata = oResult->GetValue(0, i);
    4377         177 :         const char *pszMDStandardURI = oResult->GetValue(1, i);
    4378         177 :         const char *pszMimeType = oResult->GetValue(2, i);
    4379         177 :         const char *pszReferenceScope = oResult->GetValue(3, i);
    4380         177 :         if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
    4381         177 :             pszMimeType == nullptr || pszReferenceScope == nullptr)
    4382             :         {
    4383             :             // should not happen as there are NOT NULL constraints
    4384             :             // But a database could lack such NOT NULL constraints or have
    4385             :             // large values that would cause a memory allocation failure.
    4386           0 :             continue;
    4387             :         }
    4388         177 :         int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
    4389         177 :         if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
    4390         161 :             EQUAL(pszMimeType, "text/xml"))
    4391         161 :             continue;
    4392             : 
    4393          16 :         if (!m_osRasterTable.empty() && bIsGPKGScope)
    4394             :         {
    4395           8 :             oMDMD.SetMetadataItem(
    4396             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
    4397             :                 pszMetadata, "GEOPACKAGE");
    4398           8 :             nNonGDALMDIGeopackage++;
    4399             :         }
    4400             :         /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
    4401             :         ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
    4402             :         {
    4403             :             char* apszMD[2];
    4404             :             apszMD[0] = (char*)pszMetadata;
    4405             :             apszMD[1] = NULL;
    4406             :             oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
    4407             :         }*/
    4408             :         else
    4409             :         {
    4410           8 :             oMDMD.SetMetadataItem(
    4411             :                 CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
    4412             :                 pszMetadata);
    4413           8 :             nNonGDALMDILocal++;
    4414             :         }
    4415             :     }
    4416             : 
    4417         388 :     return GDALPamDataset::GetMetadata(pszDomain);
    4418             : }
    4419             : 
    4420             : /************************************************************************/
    4421             : /*                            WriteMetadata()                           */
    4422             : /************************************************************************/
    4423             : 
    4424         607 : void GDALGeoPackageDataset::WriteMetadata(
    4425             :     CPLXMLNode *psXMLNode, /* will be destroyed by the method */
    4426             :     const char *pszTableName)
    4427             : {
    4428         607 :     const bool bIsEmpty = (psXMLNode == nullptr);
    4429         607 :     if (!HasMetadataTables())
    4430             :     {
    4431         446 :         if (bIsEmpty || !CreateMetadataTables())
    4432             :         {
    4433         205 :             CPLDestroyXMLNode(psXMLNode);
    4434         205 :             return;
    4435             :         }
    4436             :     }
    4437             : 
    4438         402 :     char *pszXML = nullptr;
    4439         402 :     if (!bIsEmpty)
    4440             :     {
    4441             :         CPLXMLNode *psMasterXMLNode =
    4442         274 :             CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
    4443         274 :         psMasterXMLNode->psChild = psXMLNode;
    4444         274 :         pszXML = CPLSerializeXMLTree(psMasterXMLNode);
    4445         274 :         CPLDestroyXMLNode(psMasterXMLNode);
    4446             :     }
    4447             :     // cppcheck-suppress uselessAssignmentPtrArg
    4448         402 :     psXMLNode = nullptr;
    4449             : 
    4450         402 :     char *pszSQL = nullptr;
    4451         402 :     if (pszTableName && pszTableName[0] != '\0')
    4452             :     {
    4453         283 :         pszSQL = sqlite3_mprintf(
    4454             :             "SELECT md.id FROM gpkg_metadata md "
    4455             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4456             :             "WHERE md.md_scope = 'dataset' AND "
    4457             :             "md.md_standard_uri='http://gdal.org' "
    4458             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
    4459             :             "lower(mdr.table_name) = lower('%q')",
    4460             :             pszTableName);
    4461             :     }
    4462             :     else
    4463             :     {
    4464         119 :         pszSQL = sqlite3_mprintf(
    4465             :             "SELECT md.id FROM gpkg_metadata md "
    4466             :             "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
    4467             :             "WHERE md.md_scope = 'dataset' AND "
    4468             :             "md.md_standard_uri='http://gdal.org' "
    4469             :             "AND md.mime_type='text/xml' AND mdr.reference_scope = "
    4470             :             "'geopackage'");
    4471             :     }
    4472             :     OGRErr err;
    4473         402 :     int mdId = SQLGetInteger(hDB, pszSQL, &err);
    4474         402 :     if (err != OGRERR_NONE)
    4475         377 :         mdId = -1;
    4476         402 :     sqlite3_free(pszSQL);
    4477             : 
    4478         402 :     if (bIsEmpty)
    4479             :     {
    4480         128 :         if (mdId >= 0)
    4481             :         {
    4482           6 :             SQLCommand(
    4483             :                 hDB,
    4484             :                 CPLSPrintf(
    4485             :                     "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
    4486             :                     mdId));
    4487           6 :             SQLCommand(
    4488             :                 hDB,
    4489             :                 CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
    4490             :         }
    4491             :     }
    4492             :     else
    4493             :     {
    4494         274 :         if (mdId >= 0)
    4495             :         {
    4496          19 :             pszSQL = sqlite3_mprintf(
    4497             :                 "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
    4498             :                 pszXML, mdId);
    4499             :         }
    4500             :         else
    4501             :         {
    4502             :             pszSQL =
    4503         255 :                 sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
    4504             :                                 "md_standard_uri, mime_type, metadata) VALUES "
    4505             :                                 "('dataset','http://gdal.org','text/xml','%q')",
    4506             :                                 pszXML);
    4507             :         }
    4508         274 :         SQLCommand(hDB, pszSQL);
    4509         274 :         sqlite3_free(pszSQL);
    4510             : 
    4511         274 :         CPLFree(pszXML);
    4512             : 
    4513         274 :         if (mdId < 0)
    4514             :         {
    4515         255 :             const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
    4516         255 :             if (pszTableName != nullptr && pszTableName[0] != '\0')
    4517             :             {
    4518         244 :                 pszSQL = sqlite3_mprintf(
    4519             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4520             :                     "table_name, timestamp, md_file_id) VALUES "
    4521             :                     "('table', '%q', %s, %d)",
    4522         488 :                     pszTableName, GetCurrentDateEscapedSQL().c_str(),
    4523             :                     static_cast<int>(nFID));
    4524             :             }
    4525             :             else
    4526             :             {
    4527          11 :                 pszSQL = sqlite3_mprintf(
    4528             :                     "INSERT INTO gpkg_metadata_reference (reference_scope, "
    4529             :                     "timestamp, md_file_id) VALUES "
    4530             :                     "('geopackage', %s, %d)",
    4531          22 :                     GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
    4532             :             }
    4533             :         }
    4534             :         else
    4535             :         {
    4536          19 :             pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
    4537             :                                      "timestamp = %s WHERE md_file_id = %d",
    4538          38 :                                      GetCurrentDateEscapedSQL().c_str(), mdId);
    4539             :         }
    4540         274 :         SQLCommand(hDB, pszSQL);
    4541         274 :         sqlite3_free(pszSQL);
    4542             :     }
    4543             : }
    4544             : 
    4545             : /************************************************************************/
    4546             : /*                        CreateMetadataTables()                        */
    4547             : /************************************************************************/
    4548             : 
    4549         253 : bool GDALGeoPackageDataset::CreateMetadataTables()
    4550             : {
    4551             :     const bool bCreateTriggers =
    4552         253 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
    4553             : 
    4554             :     /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
    4555             :     CPLString osSQL = "CREATE TABLE gpkg_metadata ("
    4556             :                       "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
    4557             :                       "md_scope TEXT NOT NULL DEFAULT 'dataset',"
    4558             :                       "md_standard_uri TEXT NOT NULL,"
    4559             :                       "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
    4560             :                       "metadata TEXT NOT NULL DEFAULT ''"
    4561         506 :                       ")";
    4562             : 
    4563             :     /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
    4564         253 :     const char *pszMetadataTriggers =
    4565             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
    4566             :         "BEFORE INSERT ON 'gpkg_metadata' "
    4567             :         "FOR EACH ROW BEGIN "
    4568             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
    4569             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4570             :         "collectionSession | series | dataset | featureType | feature | "
    4571             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4572             :         "taxonomy software | service | collectionHardware | "
    4573             :         "nonGeographicDataset | dimensionGroup') "
    4574             :         "WHERE NOT(NEW.md_scope IN "
    4575             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4576             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4577             :         "'catalogue','schema','taxonomy','software','service', "
    4578             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4579             :         "END; "
    4580             :         "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
    4581             :         "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
    4582             :         "FOR EACH ROW BEGIN "
    4583             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
    4584             :         "constraint: md_scope must be one of undefined | fieldSession | "
    4585             :         "collectionSession | series | dataset | featureType | feature | "
    4586             :         "attributeType | attribute | tile | model | catalogue | schema | "
    4587             :         "taxonomy software | service | collectionHardware | "
    4588             :         "nonGeographicDataset | dimensionGroup') "
    4589             :         "WHERE NOT(NEW.md_scope IN "
    4590             :         "('undefined','fieldSession','collectionSession','series','dataset', "
    4591             :         "'featureType','feature','attributeType','attribute','tile','model', "
    4592             :         "'catalogue','schema','taxonomy','software','service', "
    4593             :         "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
    4594             :         "END";
    4595         253 :     if (bCreateTriggers)
    4596             :     {
    4597           0 :         osSQL += ";";
    4598           0 :         osSQL += pszMetadataTriggers;
    4599             :     }
    4600             : 
    4601             :     /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
    4602             :      * Table Definition SQL */
    4603             :     osSQL += ";"
    4604             :              "CREATE TABLE gpkg_metadata_reference ("
    4605             :              "reference_scope TEXT NOT NULL,"
    4606             :              "table_name TEXT,"
    4607             :              "column_name TEXT,"
    4608             :              "row_id_value INTEGER,"
    4609             :              "timestamp DATETIME NOT NULL DEFAULT "
    4610             :              "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    4611             :              "md_file_id INTEGER NOT NULL,"
    4612             :              "md_parent_id INTEGER,"
    4613             :              "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
    4614             :              "gpkg_metadata(id),"
    4615             :              "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
    4616             :              "gpkg_metadata(id)"
    4617         253 :              ")";
    4618             : 
    4619             :     /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
    4620             :      * Definition SQL   */
    4621         253 :     const char *pszMetadataReferenceTriggers =
    4622             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_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: reference_scope must be one of \"geopackage\", "
    4627             :         "table\", \"column\", \"row\", \"row/col\"') "
    4628             :         "WHERE NOT NEW.reference_scope IN "
    4629             :         "('geopackage','table','column','row','row/col'); "
    4630             :         "END; "
    4631             :         "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
    4632             :         "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
    4633             :         "FOR EACH ROW BEGIN "
    4634             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4635             :         "violates constraint: reference_scope must be one of \"geopackage\", "
    4636             :         "\"table\", \"column\", \"row\", \"row/col\"') "
    4637             :         "WHERE NOT NEW.reference_scope IN "
    4638             :         "('geopackage','table','column','row','row/col'); "
    4639             :         "END; "
    4640             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
    4641             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4642             :         "FOR EACH ROW BEGIN "
    4643             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4644             :         "violates constraint: column name must be NULL when reference_scope "
    4645             :         "is \"geopackage\", \"table\" or \"row\"') "
    4646             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4647             :         "AND NEW.column_name IS NOT NULL); "
    4648             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4649             :         "violates constraint: column name must be defined for the specified "
    4650             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4651             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4652             :         "AND NOT NEW.table_name IN ( "
    4653             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4654             :         "AND name = NEW.table_name "
    4655             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4656             :         "END; "
    4657             :         "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
    4658             :         "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
    4659             :         "FOR EACH ROW BEGIN "
    4660             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4661             :         "violates constraint: column name must be NULL when reference_scope "
    4662             :         "is \"geopackage\", \"table\" or \"row\"') "
    4663             :         "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
    4664             :         "AND NEW.column_name IS NOT NULL); "
    4665             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4666             :         "violates constraint: column name must be defined for the specified "
    4667             :         "table when reference_scope is \"column\" or \"row/col\"') "
    4668             :         "WHERE (NEW.reference_scope IN ('column','row/col') "
    4669             :         "AND NOT NEW.table_name IN ( "
    4670             :         "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
    4671             :         "AND name = NEW.table_name "
    4672             :         "AND sql LIKE ('%' || NEW.column_name || '%'))); "
    4673             :         "END; "
    4674             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_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: row_id_value must be NULL when reference_scope "
    4679             :         "is \"geopackage\", \"table\" or \"column\"') "
    4680             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4681             :         "AND NEW.row_id_value IS NOT NULL; "
    4682             :         "END; "
    4683             :         "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
    4684             :         "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
    4685             :         "FOR EACH ROW BEGIN "
    4686             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4687             :         "violates constraint: row_id_value must be NULL when reference_scope "
    4688             :         "is \"geopackage\", \"table\" or \"column\"') "
    4689             :         "WHERE NEW.reference_scope IN ('geopackage','table','column') "
    4690             :         "AND NEW.row_id_value IS NOT NULL; "
    4691             :         "END; "
    4692             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
    4693             :         "BEFORE INSERT ON 'gpkg_metadata_reference' "
    4694             :         "FOR EACH ROW BEGIN "
    4695             :         "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
    4696             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4697             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4698             :         "WHERE NOT (NEW.timestamp GLOB "
    4699             :         "'[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-"
    4700             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4701             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4702             :         "END; "
    4703             :         "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
    4704             :         "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
    4705             :         "FOR EACH ROW BEGIN "
    4706             :         "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
    4707             :         "violates constraint: timestamp must be a valid time in ISO 8601 "
    4708             :         "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
    4709             :         "WHERE NOT (NEW.timestamp GLOB "
    4710             :         "'[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-"
    4711             :         "5][0-9].[0-9][0-9][0-9]Z' "
    4712             :         "AND strftime('%s',NEW.timestamp) NOT NULL); "
    4713             :         "END";
    4714         253 :     if (bCreateTriggers)
    4715             :     {
    4716           0 :         osSQL += ";";
    4717           0 :         osSQL += pszMetadataReferenceTriggers;
    4718             :     }
    4719             : 
    4720         253 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    4721           0 :         return false;
    4722             : 
    4723         253 :     osSQL += ";";
    4724             :     osSQL += "INSERT INTO gpkg_extensions "
    4725             :              "(table_name, column_name, extension_name, definition, scope) "
    4726             :              "VALUES "
    4727             :              "('gpkg_metadata', NULL, 'gpkg_metadata', "
    4728             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4729         253 :              "'read-write')";
    4730             : 
    4731         253 :     osSQL += ";";
    4732             :     osSQL += "INSERT INTO gpkg_extensions "
    4733             :              "(table_name, column_name, extension_name, definition, scope) "
    4734             :              "VALUES "
    4735             :              "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
    4736             :              "'http://www.geopackage.org/spec120/#extension_metadata', "
    4737         253 :              "'read-write')";
    4738             : 
    4739         253 :     const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
    4740         253 :     m_nHasMetadataTables = bOK;
    4741         253 :     return bOK;
    4742             : }
    4743             : 
    4744             : /************************************************************************/
    4745             : /*                            FlushMetadata()                           */
    4746             : /************************************************************************/
    4747             : 
    4748        7631 : void GDALGeoPackageDataset::FlushMetadata()
    4749             : {
    4750        7631 :     if (!m_bMetadataDirty || m_poParentDS != nullptr ||
    4751         307 :         m_nCreateMetadataTables == FALSE)
    4752        7330 :         return;
    4753         301 :     m_bMetadataDirty = false;
    4754             : 
    4755         301 :     if (eAccess == GA_ReadOnly)
    4756             :     {
    4757           3 :         return;
    4758             :     }
    4759             : 
    4760         298 :     bool bCanWriteAreaOrPoint =
    4761         594 :         !m_bGridCellEncodingAsCO &&
    4762         296 :         (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
    4763         298 :     if (!m_osRasterTable.empty())
    4764             :     {
    4765             :         const char *pszIdentifier =
    4766         121 :             GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
    4767             :         const char *pszDescription =
    4768         121 :             GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
    4769         148 :         if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
    4770          27 :             pszIdentifier != m_osIdentifier)
    4771             :         {
    4772          14 :             m_osIdentifier = pszIdentifier;
    4773             :             char *pszSQL =
    4774          14 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4775             :                                 "WHERE lower(table_name) = lower('%q')",
    4776             :                                 pszIdentifier, m_osRasterTable.c_str());
    4777          14 :             SQLCommand(hDB, pszSQL);
    4778          14 :             sqlite3_free(pszSQL);
    4779             :         }
    4780         128 :         if (!m_bDescriptionAsCO && pszDescription != nullptr &&
    4781           7 :             pszDescription != m_osDescription)
    4782             :         {
    4783           7 :             m_osDescription = pszDescription;
    4784             :             char *pszSQL =
    4785           7 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4786             :                                 "WHERE lower(table_name) = lower('%q')",
    4787             :                                 pszDescription, m_osRasterTable.c_str());
    4788           7 :             SQLCommand(hDB, pszSQL);
    4789           7 :             sqlite3_free(pszSQL);
    4790             :         }
    4791         121 :         if (bCanWriteAreaOrPoint)
    4792             :         {
    4793             :             const char *pszAreaOrPoint =
    4794          27 :                 GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
    4795          27 :             if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
    4796             :             {
    4797          22 :                 bCanWriteAreaOrPoint = false;
    4798          22 :                 char *pszSQL = sqlite3_mprintf(
    4799             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4800             :                     "grid_cell_encoding = 'grid-value-is-area' WHERE "
    4801             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4802             :                     m_osRasterTable.c_str());
    4803          22 :                 SQLCommand(hDB, pszSQL);
    4804          22 :                 sqlite3_free(pszSQL);
    4805             :             }
    4806           5 :             else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
    4807             :             {
    4808           1 :                 bCanWriteAreaOrPoint = false;
    4809           1 :                 char *pszSQL = sqlite3_mprintf(
    4810             :                     "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
    4811             :                     "grid_cell_encoding = 'grid-value-is-center' WHERE "
    4812             :                     "lower(tile_matrix_set_name) = lower('%q')",
    4813             :                     m_osRasterTable.c_str());
    4814           1 :                 SQLCommand(hDB, pszSQL);
    4815           1 :                 sqlite3_free(pszSQL);
    4816             :             }
    4817             :         }
    4818             :     }
    4819             : 
    4820         298 :     char **papszMDDup = nullptr;
    4821         479 :     for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
    4822         479 :          papszIter && *papszIter; ++papszIter)
    4823             :     {
    4824         181 :         if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4825          27 :             continue;
    4826         154 :         if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4827           8 :             continue;
    4828         146 :         if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
    4829          14 :             continue;
    4830         132 :         if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
    4831           4 :             continue;
    4832         128 :         if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
    4833          28 :             !bCanWriteAreaOrPoint &&
    4834          25 :             STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
    4835             :         {
    4836          25 :             continue;
    4837             :         }
    4838         103 :         papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4839             :     }
    4840             : 
    4841         298 :     CPLXMLNode *psXMLNode = nullptr;
    4842             :     {
    4843         298 :         GDALMultiDomainMetadata oLocalMDMD;
    4844         298 :         CSLConstList papszDomainList = oMDMD.GetDomainList();
    4845         298 :         CSLConstList papszIter = papszDomainList;
    4846         298 :         oLocalMDMD.SetMetadata(papszMDDup);
    4847         574 :         while (papszIter && *papszIter)
    4848             :         {
    4849         276 :             if (!EQUAL(*papszIter, "") &&
    4850         136 :                 !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
    4851          15 :                 !EQUAL(*papszIter, "GEOPACKAGE"))
    4852             :             {
    4853           8 :                 oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
    4854             :                                        *papszIter);
    4855             :             }
    4856         276 :             papszIter++;
    4857             :         }
    4858         298 :         if (m_nBandCountFromMetadata > 0)
    4859             :         {
    4860          57 :             oLocalMDMD.SetMetadataItem(
    4861             :                 "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
    4862             :                 "IMAGE_STRUCTURE");
    4863          57 :             if (nBands == 1)
    4864             :             {
    4865          36 :                 const auto poCT = GetRasterBand(1)->GetColorTable();
    4866          36 :                 if (poCT)
    4867             :                 {
    4868          16 :                     std::string osVal("{");
    4869           8 :                     const int nColorCount = poCT->GetColorEntryCount();
    4870        2056 :                     for (int i = 0; i < nColorCount; ++i)
    4871             :                     {
    4872        2048 :                         if (i > 0)
    4873        2040 :                             osVal += ',';
    4874        2048 :                         const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
    4875             :                         osVal +=
    4876        2048 :                             CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
    4877        2048 :                                        psEntry->c2, psEntry->c3, psEntry->c4);
    4878             :                     }
    4879           8 :                     osVal += '}';
    4880           8 :                     oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
    4881             :                                                "IMAGE_STRUCTURE");
    4882             :                 }
    4883             :             }
    4884          57 :             if (nBands == 1)
    4885             :             {
    4886          36 :                 const char *pszTILE_FORMAT = nullptr;
    4887          36 :                 switch (m_eTF)
    4888             :                 {
    4889           0 :                     case GPKG_TF_PNG_JPEG:
    4890           0 :                         pszTILE_FORMAT = "JPEG_PNG";
    4891           0 :                         break;
    4892          30 :                     case GPKG_TF_PNG:
    4893          30 :                         break;
    4894           0 :                     case GPKG_TF_PNG8:
    4895           0 :                         pszTILE_FORMAT = "PNG8";
    4896           0 :                         break;
    4897           3 :                     case GPKG_TF_JPEG:
    4898           3 :                         pszTILE_FORMAT = "JPEG";
    4899           3 :                         break;
    4900           3 :                     case GPKG_TF_WEBP:
    4901           3 :                         pszTILE_FORMAT = "WEBP";
    4902           3 :                         break;
    4903           0 :                     case GPKG_TF_PNG_16BIT:
    4904           0 :                         break;
    4905           0 :                     case GPKG_TF_TIFF_32BIT_FLOAT:
    4906           0 :                         break;
    4907             :                 }
    4908          36 :                 if (pszTILE_FORMAT)
    4909           6 :                     oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
    4910             :                                                "IMAGE_STRUCTURE");
    4911             :             }
    4912             :         }
    4913         419 :         if (GetRasterCount() > 0 &&
    4914         121 :             GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
    4915             :         {
    4916          92 :             int bHasNoData = FALSE;
    4917             :             const double dfNoDataValue =
    4918          92 :                 GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    4919          92 :             if (bHasNoData)
    4920             :             {
    4921           3 :                 oLocalMDMD.SetMetadataItem("NODATA_VALUE",
    4922             :                                            CPLSPrintf("%.18g", dfNoDataValue),
    4923             :                                            "IMAGE_STRUCTURE");
    4924             :             }
    4925             :         }
    4926         522 :         for (int i = 1; i <= GetRasterCount(); ++i)
    4927             :         {
    4928             :             auto poBand =
    4929         224 :                 cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
    4930         224 :             poBand->AddImplicitStatistics(false);
    4931         224 :             char **papszMD = GetRasterBand(i)->GetMetadata();
    4932         224 :             poBand->AddImplicitStatistics(true);
    4933         224 :             if (papszMD)
    4934             :             {
    4935           8 :                 oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
    4936             :             }
    4937             :         }
    4938         298 :         psXMLNode = oLocalMDMD.Serialize();
    4939             :     }
    4940             : 
    4941         298 :     CSLDestroy(papszMDDup);
    4942         298 :     papszMDDup = nullptr;
    4943             : 
    4944         298 :     WriteMetadata(psXMLNode, m_osRasterTable.c_str());
    4945             : 
    4946         298 :     if (!m_osRasterTable.empty())
    4947             :     {
    4948             :         char **papszGeopackageMD =
    4949         121 :             GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
    4950             : 
    4951         121 :         papszMDDup = nullptr;
    4952         130 :         for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
    4953             :              ++papszIter)
    4954             :         {
    4955           9 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    4956             :         }
    4957             : 
    4958         242 :         GDALMultiDomainMetadata oLocalMDMD;
    4959         121 :         oLocalMDMD.SetMetadata(papszMDDup);
    4960         121 :         CSLDestroy(papszMDDup);
    4961         121 :         papszMDDup = nullptr;
    4962         121 :         psXMLNode = oLocalMDMD.Serialize();
    4963             : 
    4964         121 :         WriteMetadata(psXMLNode, nullptr);
    4965             :     }
    4966             : 
    4967         486 :     for (int i = 0; i < m_nLayers; i++)
    4968             :     {
    4969             :         const char *pszIdentifier =
    4970         188 :             m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    4971             :         const char *pszDescription =
    4972         188 :             m_papoLayers[i]->GetMetadataItem("DESCRIPTION");
    4973         188 :         if (pszIdentifier != nullptr)
    4974             :         {
    4975             :             char *pszSQL =
    4976           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
    4977             :                                 "WHERE lower(table_name) = lower('%q')",
    4978           3 :                                 pszIdentifier, m_papoLayers[i]->GetName());
    4979           3 :             SQLCommand(hDB, pszSQL);
    4980           3 :             sqlite3_free(pszSQL);
    4981             :         }
    4982         188 :         if (pszDescription != nullptr)
    4983             :         {
    4984             :             char *pszSQL =
    4985           3 :                 sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
    4986             :                                 "WHERE lower(table_name) = lower('%q')",
    4987           3 :                                 pszDescription, m_papoLayers[i]->GetName());
    4988           3 :             SQLCommand(hDB, pszSQL);
    4989           3 :             sqlite3_free(pszSQL);
    4990             :         }
    4991             : 
    4992         188 :         papszMDDup = nullptr;
    4993         538 :         for (char **papszIter = m_papoLayers[i]->GetMetadata();
    4994         538 :              papszIter && *papszIter; ++papszIter)
    4995             :         {
    4996         350 :             if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
    4997           3 :                 continue;
    4998         347 :             if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
    4999           3 :                 continue;
    5000         344 :             if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
    5001           0 :                 continue;
    5002         344 :             papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
    5003             :         }
    5004             : 
    5005             :         {
    5006         188 :             GDALMultiDomainMetadata oLocalMDMD;
    5007         188 :             char **papszDomainList = m_papoLayers[i]->GetMetadataDomainList();
    5008         188 :             char **papszIter = papszDomainList;
    5009         188 :             oLocalMDMD.SetMetadata(papszMDDup);
    5010         396 :             while (papszIter && *papszIter)
    5011             :             {
    5012         208 :                 if (!EQUAL(*papszIter, ""))
    5013          66 :                     oLocalMDMD.SetMetadata(
    5014          33 :                         m_papoLayers[i]->GetMetadata(*papszIter), *papszIter);
    5015         208 :                 papszIter++;
    5016             :             }
    5017         188 :             CSLDestroy(papszDomainList);
    5018         188 :             psXMLNode = oLocalMDMD.Serialize();
    5019             :         }
    5020             : 
    5021         188 :         CSLDestroy(papszMDDup);
    5022         188 :         papszMDDup = nullptr;
    5023             : 
    5024         188 :         WriteMetadata(psXMLNode, m_papoLayers[i]->GetName());
    5025             :     }
    5026             : }
    5027             : 
    5028             : /************************************************************************/
    5029             : /*                          GetMetadataItem()                           */
    5030             : /************************************************************************/
    5031             : 
    5032        1252 : const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
    5033             :                                                    const char *pszDomain)
    5034             : {
    5035        1252 :     pszDomain = CheckMetadataDomain(pszDomain);
    5036        1252 :     return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
    5037             : }
    5038             : 
    5039             : /************************************************************************/
    5040             : /*                            SetMetadata()                             */
    5041             : /************************************************************************/
    5042             : 
    5043         111 : CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
    5044             :                                           const char *pszDomain)
    5045             : {
    5046         111 :     pszDomain = CheckMetadataDomain(pszDomain);
    5047         111 :     m_bMetadataDirty = true;
    5048         111 :     GetMetadata(); /* force loading from storage if needed */
    5049         111 :     return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
    5050             : }
    5051             : 
    5052             : /************************************************************************/
    5053             : /*                          SetMetadataItem()                           */
    5054             : /************************************************************************/
    5055             : 
    5056          21 : CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
    5057             :                                               const char *pszValue,
    5058             :                                               const char *pszDomain)
    5059             : {
    5060          21 :     pszDomain = CheckMetadataDomain(pszDomain);
    5061          21 :     m_bMetadataDirty = true;
    5062          21 :     GetMetadata(); /* force loading from storage if needed */
    5063          21 :     return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    5064             : }
    5065             : 
    5066             : /************************************************************************/
    5067             : /*                                Create()                              */
    5068             : /************************************************************************/
    5069             : 
    5070         735 : int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
    5071             :                                   int nYSize, int nBandsIn, GDALDataType eDT,
    5072             :                                   char **papszOptions)
    5073             : {
    5074        1470 :     CPLString osCommand;
    5075             : 
    5076             :     /* First, ensure there isn't any such file yet. */
    5077             :     VSIStatBufL sStatBuf;
    5078             : 
    5079         735 :     if (nBandsIn != 0)
    5080             :     {
    5081         197 :         if (eDT == GDT_Byte)
    5082             :         {
    5083         128 :             if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
    5084             :                 nBandsIn != 4)
    5085             :             {
    5086           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5087             :                          "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
    5088             :                          "3 (RGB) or 4 (RGBA) band dataset supported for "
    5089             :                          "Byte datatype");
    5090           1 :                 return FALSE;
    5091             :             }
    5092             :         }
    5093          69 :         else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
    5094             :         {
    5095          42 :             if (nBandsIn != 1)
    5096             :             {
    5097           3 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5098             :                          "Only single band dataset supported for non Byte "
    5099             :                          "datatype");
    5100           3 :                 return FALSE;
    5101             :             }
    5102             :         }
    5103             :         else
    5104             :         {
    5105          27 :             CPLError(CE_Failure, CPLE_NotSupported,
    5106             :                      "Only Byte, Int16, UInt16 or Float32 supported");
    5107          27 :             return FALSE;
    5108             :         }
    5109             :     }
    5110             : 
    5111         704 :     const size_t nFilenameLen = strlen(pszFilename);
    5112         704 :     const bool bGpkgZip =
    5113         699 :         (nFilenameLen > strlen(".gpkg.zip") &&
    5114        1403 :          !STARTS_WITH(pszFilename, "/vsizip/") &&
    5115         699 :          EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
    5116             : 
    5117             :     const bool bUseTempFile =
    5118         705 :         bGpkgZip || (CPLTestBool(CPLGetConfigOption(
    5119           1 :                          "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
    5120           1 :                      (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
    5121           1 :                       EQUAL(CPLGetConfigOption(
    5122             :                                 "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
    5123         704 :                             "FORCED")));
    5124             : 
    5125         704 :     bool bFileExists = false;
    5126         704 :     if (VSIStatL(pszFilename, &sStatBuf) == 0)
    5127             :     {
    5128           7 :         bFileExists = true;
    5129          14 :         if (nBandsIn == 0 || bUseTempFile ||
    5130           7 :             !CPLTestBool(
    5131             :                 CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
    5132             :         {
    5133           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5134             :                      "A file system object called '%s' already exists.",
    5135             :                      pszFilename);
    5136             : 
    5137           0 :             return FALSE;
    5138             :         }
    5139             :     }
    5140             : 
    5141         704 :     if (bUseTempFile)
    5142             :     {
    5143           3 :         if (bGpkgZip)
    5144             :         {
    5145           2 :             std::string osFilenameInZip(CPLGetFilename(pszFilename));
    5146           2 :             osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
    5147             :             m_osFinalFilename =
    5148           2 :                 std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
    5149             :         }
    5150             :         else
    5151             :         {
    5152           1 :             m_osFinalFilename = pszFilename;
    5153             :         }
    5154           3 :         m_pszFilename =
    5155           3 :             CPLStrdup(CPLGenerateTempFilename(CPLGetFilename(pszFilename)));
    5156           3 :         CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
    5157             :     }
    5158             :     else
    5159             :     {
    5160         701 :         m_pszFilename = CPLStrdup(pszFilename);
    5161             :     }
    5162         704 :     m_bNew = true;
    5163         704 :     eAccess = GA_Update;
    5164         704 :     m_bDateTimeWithTZ =
    5165         704 :         EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
    5166             :               "WITH_TZ");
    5167             : 
    5168             :     // for test/debug purposes only. true is the nominal value
    5169         704 :     m_bPNGSupports2Bands =
    5170         704 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
    5171         704 :     m_bPNGSupportsCT =
    5172         704 :         CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
    5173             : 
    5174         704 :     if (!OpenOrCreateDB(bFileExists
    5175             :                             ? SQLITE_OPEN_READWRITE
    5176             :                             : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
    5177           4 :         return FALSE;
    5178             : 
    5179             :     /* Default to synchronous=off for performance for new file */
    5180        1393 :     if (!bFileExists &&
    5181         693 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5182             :     {
    5183         268 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5184             :     }
    5185             : 
    5186             :     /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
    5187             :     /* will be written into the main file and supported henceforth */
    5188         700 :     SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
    5189             : 
    5190         700 :     if (bFileExists)
    5191             :     {
    5192           7 :         VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
    5193           7 :         if (fp)
    5194             :         {
    5195             :             GByte abyHeader[100];
    5196           7 :             VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
    5197           7 :             VSIFCloseL(fp);
    5198             : 
    5199           7 :             memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
    5200           7 :             m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
    5201           7 :             memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
    5202           7 :             m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
    5203             : 
    5204           7 :             if (m_nApplicationId == GP10_APPLICATION_ID)
    5205             :             {
    5206           0 :                 CPLDebug("GPKG", "GeoPackage v1.0");
    5207             :             }
    5208           7 :             else if (m_nApplicationId == GP11_APPLICATION_ID)
    5209             :             {
    5210           0 :                 CPLDebug("GPKG", "GeoPackage v1.1");
    5211             :             }
    5212           7 :             else if (m_nApplicationId == GPKG_APPLICATION_ID &&
    5213           7 :                      m_nUserVersion >= GPKG_1_2_VERSION)
    5214             :             {
    5215           7 :                 CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
    5216           7 :                          (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
    5217             :             }
    5218             :         }
    5219             : 
    5220           7 :         DetectSpatialRefSysColumns();
    5221             :     }
    5222             : 
    5223         700 :     const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
    5224         700 :     if (pszVersion && !EQUAL(pszVersion, "AUTO"))
    5225             :     {
    5226          33 :         if (EQUAL(pszVersion, "1.0"))
    5227             :         {
    5228           2 :             m_nApplicationId = GP10_APPLICATION_ID;
    5229           2 :             m_nUserVersion = 0;
    5230             :         }
    5231          31 :         else if (EQUAL(pszVersion, "1.1"))
    5232             :         {
    5233           1 :             m_nApplicationId = GP11_APPLICATION_ID;
    5234           1 :             m_nUserVersion = 0;
    5235             :         }
    5236          30 :         else if (EQUAL(pszVersion, "1.2"))
    5237             :         {
    5238          12 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5239          12 :             m_nUserVersion = GPKG_1_2_VERSION;
    5240             :         }
    5241          18 :         else if (EQUAL(pszVersion, "1.3"))
    5242             :         {
    5243           3 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5244           3 :             m_nUserVersion = GPKG_1_3_VERSION;
    5245             :         }
    5246          15 :         else if (EQUAL(pszVersion, "1.4"))
    5247             :         {
    5248          15 :             m_nApplicationId = GPKG_APPLICATION_ID;
    5249          15 :             m_nUserVersion = GPKG_1_4_VERSION;
    5250             :         }
    5251             :     }
    5252             : 
    5253         700 :     SoftStartTransaction();
    5254             : 
    5255        1400 :     CPLString osSQL;
    5256         700 :     if (!bFileExists)
    5257             :     {
    5258             :         /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
    5259             :          * table */
    5260             :         /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5261             :         osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
    5262             :                 "srs_name TEXT NOT NULL,"
    5263             :                 "srs_id INTEGER NOT NULL PRIMARY KEY,"
    5264             :                 "organization TEXT NOT NULL,"
    5265             :                 "organization_coordsys_id INTEGER NOT NULL,"
    5266             :                 "definition  TEXT NOT NULL,"
    5267         693 :                 "description TEXT";
    5268         693 :         if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
    5269         849 :                                              "NO")) ||
    5270         156 :             (nBandsIn != 0 && eDT != GDT_Byte))
    5271             :         {
    5272          41 :             m_bHasDefinition12_063 = true;
    5273          41 :             osSQL += ", definition_12_063 TEXT NOT NULL";
    5274          41 :             if (m_nUserVersion >= GPKG_1_4_VERSION)
    5275             :             {
    5276           1 :                 osSQL += ", epoch DOUBLE";
    5277           1 :                 m_bHasEpochColumn = true;
    5278             :             }
    5279             :         }
    5280             :         osSQL += ")"
    5281             :                  ";"
    5282             :                  /* Requirement 11: The gpkg_spatial_ref_sys table in a
    5283             :                     GeoPackage SHALL */
    5284             :                  /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
    5285             :                  /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5286             : 
    5287             :                  "INSERT INTO gpkg_spatial_ref_sys ("
    5288             :                  "srs_name, srs_id, organization, organization_coordsys_id, "
    5289         693 :                  "definition, description";
    5290         693 :         if (m_bHasDefinition12_063)
    5291          41 :             osSQL += ", definition_12_063";
    5292             :         osSQL +=
    5293             :             ") VALUES ("
    5294             :             "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
    5295             :             "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
    5296             :             "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
    5297             :             "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
    5298             :             "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
    5299             :             "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
    5300             :             "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
    5301             :             "', 'longitude/latitude coordinates in decimal degrees on the WGS "
    5302         693 :             "84 spheroid'";
    5303         693 :         if (m_bHasDefinition12_063)
    5304             :             osSQL +=
    5305             :                 ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
    5306             :                 "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
    5307             :                 "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
    5308             :                 "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
    5309             :                 "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
    5310             :                 "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
    5311          41 :                 "ID[\"EPSG\", 4326]]'";
    5312             :         osSQL +=
    5313             :             ")"
    5314             :             ";"
    5315             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5316             :                SHALL */
    5317             :             /* contain a record with an srs_id of -1, an organization of “NONE”,
    5318             :              */
    5319             :             /* an organization_coordsys_id of -1, and definition “undefined” */
    5320             :             /* for undefined Cartesian coordinate reference systems */
    5321             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5322             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5323             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5324         693 :             "definition, description";
    5325         693 :         if (m_bHasDefinition12_063)
    5326          41 :             osSQL += ", definition_12_063";
    5327             :         osSQL += ") VALUES ("
    5328             :                  "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
    5329         693 :                  "'undefined Cartesian coordinate reference system'";
    5330         693 :         if (m_bHasDefinition12_063)
    5331          41 :             osSQL += ", 'undefined'";
    5332             :         osSQL +=
    5333             :             ")"
    5334             :             ";"
    5335             :             /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
    5336             :                SHALL */
    5337             :             /* contain a record with an srs_id of 0, an organization of “NONE”,
    5338             :              */
    5339             :             /* an organization_coordsys_id of 0, and definition “undefined” */
    5340             :             /* for undefined geographic coordinate reference systems */
    5341             :             /* http://opengis.github.io/geopackage/#spatial_ref_sys */
    5342             :             "INSERT INTO gpkg_spatial_ref_sys ("
    5343             :             "srs_name, srs_id, organization, organization_coordsys_id, "
    5344         693 :             "definition, description";
    5345         693 :         if (m_bHasDefinition12_063)
    5346          41 :             osSQL += ", definition_12_063";
    5347             :         osSQL += ") VALUES ("
    5348             :                  "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
    5349         693 :                  "'undefined geographic coordinate reference system'";
    5350         693 :         if (m_bHasDefinition12_063)
    5351          41 :             osSQL += ", 'undefined'";
    5352             :         osSQL += ")"
    5353             :                  ";"
    5354             :                  /* Requirement 13: A GeoPackage file SHALL include a
    5355             :                     gpkg_contents table */
    5356             :                  /* http://opengis.github.io/geopackage/#_contents */
    5357             :                  "CREATE TABLE gpkg_contents ("
    5358             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5359             :                  "data_type TEXT NOT NULL,"
    5360             :                  "identifier TEXT UNIQUE,"
    5361             :                  "description TEXT DEFAULT '',"
    5362             :                  "last_change DATETIME NOT NULL DEFAULT "
    5363             :                  "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
    5364             :                  "min_x DOUBLE, min_y DOUBLE,"
    5365             :                  "max_x DOUBLE, max_y DOUBLE,"
    5366             :                  "srs_id INTEGER,"
    5367             :                  "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
    5368             :                  "gpkg_spatial_ref_sys(srs_id)"
    5369         693 :                  ")";
    5370             : 
    5371             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5372         693 :         if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
    5373             :         {
    5374         691 :             m_bHasGPKGOGRContents = true;
    5375             :             osSQL += ";"
    5376             :                      "CREATE TABLE gpkg_ogr_contents("
    5377             :                      "table_name TEXT NOT NULL PRIMARY KEY,"
    5378             :                      "feature_count INTEGER DEFAULT NULL"
    5379         691 :                      ")";
    5380             :         }
    5381             : #endif
    5382             : 
    5383             :         /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
    5384             :          * “features” */
    5385             :         /* data_type SHALL contain a gpkg_geometry_columns table or updateable
    5386             :          * view */
    5387             :         /* http://opengis.github.io/geopackage/#_geometry_columns */
    5388             :         const bool bCreateGeometryColumns =
    5389         693 :             CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
    5390         693 :         if (bCreateGeometryColumns)
    5391             :         {
    5392         692 :             m_bHasGPKGGeometryColumns = true;
    5393         692 :             osSQL += ";";
    5394         692 :             osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
    5395             :         }
    5396             :     }
    5397             : 
    5398             :     const bool bCreateTriggers =
    5399         700 :         CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
    5400           7 :     if ((bFileExists && nBandsIn != 0 &&
    5401           7 :          SQLGetInteger(
    5402             :              hDB,
    5403             :              "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
    5404             :              "AND type in ('table', 'view')",
    5405        1400 :              nullptr) == 0) ||
    5406         699 :         (!bFileExists &&
    5407         693 :          CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
    5408             :     {
    5409         693 :         if (!osSQL.empty())
    5410         692 :             osSQL += ";";
    5411             : 
    5412             :         /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
    5413             :          * Creation SQL  */
    5414             :         osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
    5415             :                  "table_name TEXT NOT NULL PRIMARY KEY,"
    5416             :                  "srs_id INTEGER NOT NULL,"
    5417             :                  "min_x DOUBLE NOT NULL,"
    5418             :                  "min_y DOUBLE NOT NULL,"
    5419             :                  "max_x DOUBLE NOT NULL,"
    5420             :                  "max_y DOUBLE NOT NULL,"
    5421             :                  "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
    5422             :                  "REFERENCES gpkg_contents(table_name),"
    5423             :                  "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
    5424             :                  "gpkg_spatial_ref_sys (srs_id)"
    5425             :                  ")"
    5426             :                  ";"
    5427             : 
    5428             :                  /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
    5429             :                     Creation SQL */
    5430             :                  "CREATE TABLE gpkg_tile_matrix ("
    5431             :                  "table_name TEXT NOT NULL,"
    5432             :                  "zoom_level INTEGER NOT NULL,"
    5433             :                  "matrix_width INTEGER NOT NULL,"
    5434             :                  "matrix_height INTEGER NOT NULL,"
    5435             :                  "tile_width INTEGER NOT NULL,"
    5436             :                  "tile_height INTEGER NOT NULL,"
    5437             :                  "pixel_x_size DOUBLE NOT NULL,"
    5438             :                  "pixel_y_size DOUBLE NOT NULL,"
    5439             :                  "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
    5440             :                  "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
    5441             :                  "REFERENCES gpkg_contents(table_name)"
    5442         693 :                  ")";
    5443             : 
    5444         693 :         if (bCreateTriggers)
    5445             :         {
    5446             :             /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
    5447             :              * Definition SQL */
    5448         693 :             const char *pszTileMatrixTrigger =
    5449             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
    5450             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5451             :                 "FOR EACH ROW BEGIN "
    5452             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5453             :                 "violates constraint: zoom_level cannot be less than 0') "
    5454             :                 "WHERE (NEW.zoom_level < 0); "
    5455             :                 "END; "
    5456             :                 "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
    5457             :                 "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
    5458             :                 "FOR EACH ROW BEGIN "
    5459             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5460             :                 "violates constraint: zoom_level cannot be less than 0') "
    5461             :                 "WHERE (NEW.zoom_level < 0); "
    5462             :                 "END; "
    5463             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
    5464             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5465             :                 "FOR EACH ROW BEGIN "
    5466             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5467             :                 "violates constraint: matrix_width cannot be less than 1') "
    5468             :                 "WHERE (NEW.matrix_width < 1); "
    5469             :                 "END; "
    5470             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
    5471             :                 "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
    5472             :                 "FOR EACH ROW BEGIN "
    5473             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5474             :                 "violates constraint: matrix_width cannot be less than 1') "
    5475             :                 "WHERE (NEW.matrix_width < 1); "
    5476             :                 "END; "
    5477             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
    5478             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5479             :                 "FOR EACH ROW BEGIN "
    5480             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5481             :                 "violates constraint: matrix_height cannot be less than 1') "
    5482             :                 "WHERE (NEW.matrix_height < 1); "
    5483             :                 "END; "
    5484             :                 "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
    5485             :                 "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
    5486             :                 "FOR EACH ROW BEGIN "
    5487             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5488             :                 "violates constraint: matrix_height cannot be less than 1') "
    5489             :                 "WHERE (NEW.matrix_height < 1); "
    5490             :                 "END; "
    5491             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
    5492             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5493             :                 "FOR EACH ROW BEGIN "
    5494             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5495             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5496             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5497             :                 "END; "
    5498             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
    5499             :                 "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
    5500             :                 "FOR EACH ROW BEGIN "
    5501             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5502             :                 "violates constraint: pixel_x_size must be greater than 0') "
    5503             :                 "WHERE NOT (NEW.pixel_x_size > 0); "
    5504             :                 "END; "
    5505             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
    5506             :                 "BEFORE INSERT ON 'gpkg_tile_matrix' "
    5507             :                 "FOR EACH ROW BEGIN "
    5508             :                 "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
    5509             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5510             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5511             :                 "END; "
    5512             :                 "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
    5513             :                 "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
    5514             :                 "FOR EACH ROW BEGIN "
    5515             :                 "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
    5516             :                 "violates constraint: pixel_y_size must be greater than 0') "
    5517             :                 "WHERE NOT (NEW.pixel_y_size > 0); "
    5518             :                 "END;";
    5519         693 :             osSQL += ";";
    5520         693 :             osSQL += pszTileMatrixTrigger;
    5521             :         }
    5522             :     }
    5523             : 
    5524         700 :     if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
    5525           0 :         return FALSE;
    5526             : 
    5527         700 :     if (!bFileExists)
    5528             :     {
    5529             :         const char *pszMetadataTables =
    5530         693 :             CSLFetchNameValue(papszOptions, "METADATA_TABLES");
    5531         693 :         if (pszMetadataTables)
    5532           6 :             m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
    5533             : 
    5534         693 :         if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
    5535           0 :             return FALSE;
    5536             : 
    5537         693 :         if (m_bHasDefinition12_063)
    5538             :         {
    5539          82 :             if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
    5540             :                 OGRERR_NONE !=
    5541          41 :                     SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5542             :                                     "(table_name, column_name, extension_name, "
    5543             :                                     "definition, scope) "
    5544             :                                     "VALUES "
    5545             :                                     "('gpkg_spatial_ref_sys', "
    5546             :                                     "'definition_12_063', 'gpkg_crs_wkt', "
    5547             :                                     "'http://www.geopackage.org/spec120/"
    5548             :                                     "#extension_crs_wkt', 'read-write')"))
    5549             :             {
    5550           0 :                 return FALSE;
    5551             :             }
    5552          41 :             if (m_bHasEpochColumn)
    5553             :             {
    5554           1 :                 if (OGRERR_NONE !=
    5555           1 :                         SQLCommand(
    5556             :                             hDB, "UPDATE gpkg_extensions SET extension_name = "
    5557             :                                  "'gpkg_crs_wkt_1_1' "
    5558           2 :                                  "WHERE extension_name = 'gpkg_crs_wkt'") ||
    5559             :                     OGRERR_NONE !=
    5560           1 :                         SQLCommand(hDB, "INSERT INTO gpkg_extensions "
    5561             :                                         "(table_name, column_name, "
    5562             :                                         "extension_name, definition, scope) "
    5563             :                                         "VALUES "
    5564             :                                         "('gpkg_spatial_ref_sys', 'epoch', "
    5565             :                                         "'gpkg_crs_wkt_1_1', "
    5566             :                                         "'http://www.geopackage.org/spec/"
    5567             :                                         "#extension_crs_wkt', "
    5568             :                                         "'read-write')"))
    5569             :                 {
    5570           0 :                     return FALSE;
    5571             :                 }
    5572             :             }
    5573             :         }
    5574             :     }
    5575             : 
    5576         700 :     if (nBandsIn != 0)
    5577             :     {
    5578         163 :         const char *pszTableName = CPLGetBasename(m_pszFilename);
    5579             :         m_osRasterTable =
    5580         163 :             CSLFetchNameValueDef(papszOptions, "RASTER_TABLE", pszTableName);
    5581         163 :         if (m_osRasterTable.empty())
    5582             :         {
    5583           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5584             :                      "RASTER_TABLE must be set to a non empty value");
    5585           0 :             return FALSE;
    5586             :         }
    5587         163 :         m_bIdentifierAsCO =
    5588         163 :             CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
    5589             :         m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
    5590         163 :                                               m_osRasterTable);
    5591         163 :         m_bDescriptionAsCO =
    5592         163 :             CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
    5593             :         m_osDescription =
    5594         163 :             CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
    5595         163 :         SetDataType(eDT);
    5596         163 :         if (eDT == GDT_Int16)
    5597          15 :             SetGlobalOffsetScale(-32768.0, 1.0);
    5598             : 
    5599             :         /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
    5600             :          * table Create Table SQL (Informative) */
    5601             :         char *pszSQL =
    5602         163 :             sqlite3_mprintf("CREATE TABLE \"%w\" ("
    5603             :                             "id INTEGER PRIMARY KEY AUTOINCREMENT,"
    5604             :                             "zoom_level INTEGER NOT NULL,"
    5605             :                             "tile_column INTEGER NOT NULL,"
    5606             :                             "tile_row INTEGER NOT NULL,"
    5607             :                             "tile_data BLOB NOT NULL,"
    5608             :                             "UNIQUE (zoom_level, tile_column, tile_row)"
    5609             :                             ")",
    5610             :                             m_osRasterTable.c_str());
    5611         163 :         osSQL = pszSQL;
    5612         163 :         sqlite3_free(pszSQL);
    5613             : 
    5614         163 :         if (bCreateTriggers)
    5615             :         {
    5616             :             /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
    5617             :              * Definition SQL  */
    5618         163 :             pszSQL = sqlite3_mprintf(
    5619             :                 "CREATE TRIGGER \"%w_zoom_insert\" "
    5620             :                 "BEFORE INSERT ON \"%w\" "
    5621             :                 "FOR EACH ROW BEGIN "
    5622             :                 "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    5623             :                 "constraint: zoom_level not specified for table in "
    5624             :                 "gpkg_tile_matrix') "
    5625             :                 "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    5626             :                 "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    5627             :                 "END; "
    5628             :                 "CREATE TRIGGER \"%w_zoom_update\" "
    5629             :                 "BEFORE UPDATE OF zoom_level ON \"%w\" "
    5630             :                 "FOR EACH ROW BEGIN "
    5631             :                 "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    5632             :                 "constraint: zoom_level not specified for table in "
    5633             :                 "gpkg_tile_matrix') "
    5634             :                 "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
    5635             :                 "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
    5636             :                 "END; "
    5637             :                 "CREATE TRIGGER \"%w_tile_column_insert\" "
    5638             :                 "BEFORE INSERT ON \"%w\" "
    5639             :                 "FOR EACH ROW BEGIN "
    5640             :                 "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    5641             :                 "constraint: tile_column cannot be < 0') "
    5642             :                 "WHERE (NEW.tile_column < 0) ; "
    5643             :                 "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    5644             :                 "constraint: tile_column must by < matrix_width specified for "
    5645             :                 "table and zoom level in gpkg_tile_matrix') "
    5646             :                 "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    5647             :                 "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    5648             :                 "zoom_level = NEW.zoom_level)); "
    5649             :                 "END; "
    5650             :                 "CREATE TRIGGER \"%w_tile_column_update\" "
    5651             :                 "BEFORE UPDATE OF tile_column ON \"%w\" "
    5652             :                 "FOR EACH ROW BEGIN "
    5653             :                 "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    5654             :                 "constraint: tile_column cannot be < 0') "
    5655             :                 "WHERE (NEW.tile_column < 0) ; "
    5656             :                 "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    5657             :                 "constraint: tile_column must by < matrix_width specified for "
    5658             :                 "table and zoom level in gpkg_tile_matrix') "
    5659             :                 "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
    5660             :                 "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    5661             :                 "zoom_level = NEW.zoom_level)); "
    5662             :                 "END; "
    5663             :                 "CREATE TRIGGER \"%w_tile_row_insert\" "
    5664             :                 "BEFORE INSERT ON \"%w\" "
    5665             :                 "FOR EACH ROW BEGIN "
    5666             :                 "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    5667             :                 "constraint: tile_row cannot be < 0') "
    5668             :                 "WHERE (NEW.tile_row < 0) ; "
    5669             :                 "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
    5670             :                 "constraint: tile_row must by < matrix_height specified for "
    5671             :                 "table and zoom level in gpkg_tile_matrix') "
    5672             :                 "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    5673             :                 "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    5674             :                 "zoom_level = NEW.zoom_level)); "
    5675             :                 "END; "
    5676             :                 "CREATE TRIGGER \"%w_tile_row_update\" "
    5677             :                 "BEFORE UPDATE OF tile_row ON \"%w\" "
    5678             :                 "FOR EACH ROW BEGIN "
    5679             :                 "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    5680             :                 "constraint: tile_row cannot be < 0') "
    5681             :                 "WHERE (NEW.tile_row < 0) ; "
    5682             :                 "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
    5683             :                 "constraint: tile_row must by < matrix_height specified for "
    5684             :                 "table and zoom level in gpkg_tile_matrix') "
    5685             :                 "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
    5686             :                 "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
    5687             :                 "zoom_level = NEW.zoom_level)); "
    5688             :                 "END; ",
    5689             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5690             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5691             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5692             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5693             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5694             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5695             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5696             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5697             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5698             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5699             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5700             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5701             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str(),
    5702             :                 m_osRasterTable.c_str(), m_osRasterTable.c_str());
    5703             : 
    5704         163 :             osSQL += ";";
    5705         163 :             osSQL += pszSQL;
    5706         163 :             sqlite3_free(pszSQL);
    5707             :         }
    5708             : 
    5709         163 :         OGRErr eErr = SQLCommand(hDB, osSQL);
    5710         163 :         if (OGRERR_NONE != eErr)
    5711           0 :             return FALSE;
    5712             : 
    5713         163 :         const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
    5714         163 :         if (eDT == GDT_Int16 || eDT == GDT_UInt16)
    5715             :         {
    5716          26 :             m_eTF = GPKG_TF_PNG_16BIT;
    5717          26 :             if (pszTF)
    5718             :             {
    5719           0 :                 if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
    5720             :                 {
    5721           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5722             :                              "Only AUTO or PNG supported "
    5723             :                              "as tile format for Int16 / UInt16");
    5724             :                 }
    5725             :             }
    5726             :         }
    5727         137 :         else if (eDT == GDT_Float32)
    5728             :         {
    5729          13 :             m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
    5730          13 :             if (pszTF)
    5731             :             {
    5732           5 :                 if (EQUAL(pszTF, "PNG"))
    5733           5 :                     m_eTF = GPKG_TF_PNG_16BIT;
    5734           0 :                 else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
    5735             :                 {
    5736           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    5737             :                              "Only AUTO, PNG or TIFF supported "
    5738             :                              "as tile format for Float32");
    5739             :                 }
    5740             :             }
    5741             :         }
    5742             :         else
    5743             :         {
    5744         124 :             if (pszTF)
    5745             :             {
    5746          70 :                 m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
    5747          70 :                 if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
    5748           7 :                     m_bMetadataDirty = true;
    5749             :             }
    5750          54 :             else if (nBandsIn == 1)
    5751          43 :                 m_eTF = GPKG_TF_PNG;
    5752             :         }
    5753             : 
    5754         163 :         if (eDT != GDT_Byte)
    5755             :         {
    5756          39 :             if (!CreateTileGriddedTable(papszOptions))
    5757           0 :                 return FALSE;
    5758             :         }
    5759             : 
    5760         163 :         nRasterXSize = nXSize;
    5761         163 :         nRasterYSize = nYSize;
    5762             : 
    5763             :         const char *pszTileSize =
    5764         163 :             CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
    5765             :         const char *pszTileWidth =
    5766         163 :             CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
    5767             :         const char *pszTileHeight =
    5768         163 :             CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
    5769         163 :         int nTileWidth = atoi(pszTileWidth);
    5770         163 :         int nTileHeight = atoi(pszTileHeight);
    5771         163 :         if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
    5772         326 :              nTileHeight > 4096) &&
    5773           1 :             !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
    5774             :         {
    5775           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    5776             :                      "Invalid block dimensions: %dx%d", nTileWidth,
    5777             :                      nTileHeight);
    5778           0 :             return FALSE;
    5779             :         }
    5780             : 
    5781         457 :         for (int i = 1; i <= nBandsIn; i++)
    5782         294 :             SetBand(
    5783         294 :                 i, new GDALGeoPackageRasterBand(this, nTileWidth, nTileHeight));
    5784             : 
    5785         163 :         GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    5786             :                                         "IMAGE_STRUCTURE");
    5787         163 :         GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
    5788         163 :         if (!m_osDescription.empty())
    5789           1 :             GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
    5790             : 
    5791         163 :         ParseCompressionOptions(papszOptions);
    5792             : 
    5793         163 :         if (m_eTF == GPKG_TF_WEBP)
    5794             :         {
    5795          10 :             if (!RegisterWebPExtension())
    5796           0 :                 return FALSE;
    5797             :         }
    5798             : 
    5799             :         m_osTilingScheme =
    5800         163 :             CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    5801         163 :         if (!EQUAL(m_osTilingScheme, "CUSTOM"))
    5802             :         {
    5803          22 :             const auto poTS = GetTilingScheme(m_osTilingScheme);
    5804          22 :             if (!poTS)
    5805           0 :                 return FALSE;
    5806             : 
    5807          43 :             if (nTileWidth != poTS->nTileWidth ||
    5808          21 :                 nTileHeight != poTS->nTileHeight)
    5809             :             {
    5810           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
    5811             :                          "Tile dimension should be %dx%d for %s tiling scheme",
    5812           1 :                          poTS->nTileWidth, poTS->nTileHeight,
    5813             :                          m_osTilingScheme.c_str());
    5814           1 :                 return FALSE;
    5815             :             }
    5816             : 
    5817             :             const char *pszZoomLevel =
    5818          21 :                 CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    5819          21 :             if (pszZoomLevel)
    5820             :             {
    5821           1 :                 m_nZoomLevel = atoi(pszZoomLevel);
    5822           1 :                 int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    5823           1 :                 while ((1 << nMaxZoomLevelForThisTM) >
    5824           2 :                            INT_MAX / poTS->nTileXCountZoomLevel0 ||
    5825           1 :                        (1 << nMaxZoomLevelForThisTM) >
    5826           1 :                            INT_MAX / poTS->nTileYCountZoomLevel0)
    5827             :                 {
    5828           0 :                     --nMaxZoomLevelForThisTM;
    5829             :                 }
    5830             : 
    5831           1 :                 if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
    5832             :                 {
    5833           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    5834             :                              "ZOOM_LEVEL = %s is invalid. It should be in "
    5835             :                              "[0,%d] range",
    5836             :                              pszZoomLevel, nMaxZoomLevelForThisTM);
    5837           0 :                     return FALSE;
    5838             :                 }
    5839             :             }
    5840             : 
    5841             :             // Implicitly sets SRS.
    5842          21 :             OGRSpatialReference oSRS;
    5843          21 :             if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
    5844           0 :                 return FALSE;
    5845          21 :             char *pszWKT = nullptr;
    5846          21 :             oSRS.exportToWkt(&pszWKT);
    5847          21 :             SetProjection(pszWKT);
    5848          21 :             CPLFree(pszWKT);
    5849             :         }
    5850             :         else
    5851             :         {
    5852         141 :             if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    5853             :             {
    5854           0 :                 CPLError(
    5855             :                     CE_Failure, CPLE_NotSupported,
    5856             :                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    5857           0 :                 return false;
    5858             :             }
    5859             :         }
    5860             :     }
    5861             : 
    5862         699 :     if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
    5863             :     {
    5864             :         // If there was an ogr_empty_table table, we can remove it
    5865           6 :         RemoveOGREmptyTable();
    5866             :     }
    5867             : 
    5868         699 :     SoftCommitTransaction();
    5869             : 
    5870             :     /* Requirement 2 */
    5871             :     /* We have to do this after there's some content so the database file */
    5872             :     /* is not zero length */
    5873         699 :     SetApplicationAndUserVersionId();
    5874             : 
    5875             :     /* Default to synchronous=off for performance for new file */
    5876        1391 :     if (!bFileExists &&
    5877         692 :         CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
    5878             :     {
    5879         268 :         SQLCommand(hDB, "PRAGMA synchronous = OFF");
    5880             :     }
    5881             : 
    5882         699 :     return TRUE;
    5883             : }
    5884             : 
    5885             : /************************************************************************/
    5886             : /*                        RemoveOGREmptyTable()                         */
    5887             : /************************************************************************/
    5888             : 
    5889         526 : void GDALGeoPackageDataset::RemoveOGREmptyTable()
    5890             : {
    5891             :     // Run with sqlite3_exec since we don't want errors to be emitted
    5892         526 :     sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
    5893             :                  nullptr);
    5894         526 :     sqlite3_exec(
    5895             :         hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
    5896             :         nullptr, nullptr, nullptr);
    5897             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    5898         526 :     if (m_bHasGPKGOGRContents)
    5899             :     {
    5900         517 :         sqlite3_exec(hDB,
    5901             :                      "DELETE FROM gpkg_ogr_contents WHERE "
    5902             :                      "table_name = 'ogr_empty_table'",
    5903             :                      nullptr, nullptr, nullptr);
    5904             :     }
    5905             : #endif
    5906         526 :     sqlite3_exec(hDB,
    5907             :                  "DELETE FROM gpkg_geometry_columns WHERE "
    5908             :                  "table_name = 'ogr_empty_table'",
    5909             :                  nullptr, nullptr, nullptr);
    5910         526 : }
    5911             : 
    5912             : /************************************************************************/
    5913             : /*                        CreateTileGriddedTable()                      */
    5914             : /************************************************************************/
    5915             : 
    5916          39 : bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
    5917             : {
    5918          78 :     CPLString osSQL;
    5919          39 :     if (!HasGriddedCoverageAncillaryTable())
    5920             :     {
    5921             :         // It doesn't exist. So create gpkg_extensions table if necessary, and
    5922             :         // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
    5923             :         // and register them as extensions.
    5924          39 :         if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    5925           0 :             return false;
    5926             : 
    5927             :         // Req 1 /table-defs/coverage-ancillary
    5928             :         osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
    5929             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5930             :                 "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
    5931             :                 "datatype TEXT NOT NULL DEFAULT 'integer',"
    5932             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5933             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5934             :                 "precision REAL DEFAULT 1.0,"
    5935             :                 "data_null REAL,"
    5936             :                 "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
    5937             :                 "uom TEXT,"
    5938             :                 "field_name TEXT DEFAULT 'Height',"
    5939             :                 "quantity_definition TEXT DEFAULT 'Height',"
    5940             :                 "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
    5941             :                 "REFERENCES gpkg_tile_matrix_set ( table_name ) "
    5942             :                 "CHECK (datatype in ('integer','float')))"
    5943             :                 ";"
    5944             :                 // Requirement 2 /table-defs/tile-ancillary
    5945             :                 "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
    5946             :                 "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
    5947             :                 "tpudt_name TEXT NOT NULL,"
    5948             :                 "tpudt_id INTEGER NOT NULL,"
    5949             :                 "scale REAL NOT NULL DEFAULT 1.0,"
    5950             :                 "offset REAL NOT NULL DEFAULT 0.0,"
    5951             :                 "min REAL DEFAULT NULL,"
    5952             :                 "max REAL DEFAULT NULL,"
    5953             :                 "mean REAL DEFAULT NULL,"
    5954             :                 "std_dev REAL DEFAULT NULL,"
    5955             :                 "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
    5956             :                 "REFERENCES gpkg_contents(table_name),"
    5957             :                 "UNIQUE (tpudt_name, tpudt_id))"
    5958             :                 ";"
    5959             :                 // Requirement 6 /gpkg-extensions
    5960             :                 "INSERT INTO gpkg_extensions "
    5961             :                 "(table_name, column_name, extension_name, definition, scope) "
    5962             :                 "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
    5963             :                 "'gpkg_2d_gridded_coverage', "
    5964             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5965             :                 "'read-write')"
    5966             :                 ";"
    5967             :                 // Requirement 6 /gpkg-extensions
    5968             :                 "INSERT INTO gpkg_extensions "
    5969             :                 "(table_name, column_name, extension_name, definition, scope) "
    5970             :                 "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
    5971             :                 "'gpkg_2d_gridded_coverage', "
    5972             :                 "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5973             :                 "'read-write')"
    5974          39 :                 ";";
    5975             :     }
    5976             : 
    5977             :     // Requirement 6 /gpkg-extensions
    5978          39 :     char *pszSQL = sqlite3_mprintf(
    5979             :         "INSERT INTO gpkg_extensions "
    5980             :         "(table_name, column_name, extension_name, definition, scope) "
    5981             :         "VALUES ('%q', 'tile_data', "
    5982             :         "'gpkg_2d_gridded_coverage', "
    5983             :         "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
    5984             :         "'read-write')",
    5985             :         m_osRasterTable.c_str());
    5986          39 :     osSQL += pszSQL;
    5987          39 :     osSQL += ";";
    5988          39 :     sqlite3_free(pszSQL);
    5989             : 
    5990             :     // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
    5991             :     // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
    5992             :     // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
    5993          39 :     m_dfPrecision =
    5994          39 :         CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
    5995             :     CPLString osGridCellEncoding(CSLFetchNameValueDef(
    5996          78 :         papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
    5997          39 :     m_bGridCellEncodingAsCO =
    5998          39 :         CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
    5999          78 :     CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
    6000             :     CPLString osFieldName(
    6001          78 :         CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
    6002             :     CPLString osQuantityDefinition(
    6003          78 :         CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
    6004             : 
    6005         118 :     pszSQL = sqlite3_mprintf(
    6006             :         "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
    6007             :         "(tile_matrix_set_name, datatype, scale, offset, precision, "
    6008             :         "grid_cell_encoding, uom, field_name, quantity_definition) "
    6009             :         "VALUES (%Q, '%s', %.18g, %.18g, %.18g, %Q, %Q, %Q, %Q)",
    6010             :         m_osRasterTable.c_str(),
    6011          39 :         (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
    6012             :         m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
    6013          40 :         osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
    6014             :         osQuantityDefinition.c_str());
    6015          39 :     m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
    6016          39 :     sqlite3_free(pszSQL);
    6017             : 
    6018             :     // Requirement 3 /gpkg-spatial-ref-sys-row
    6019             :     auto oResultTable = SQLQuery(
    6020          78 :         hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
    6021          39 :     bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
    6022          39 :     if (!bHasEPSG4979)
    6023             :     {
    6024          40 :         if (!m_bHasDefinition12_063 &&
    6025           1 :             !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
    6026             :         {
    6027           0 :             return false;
    6028             :         }
    6029             : 
    6030             :         // This is WKT 2...
    6031          39 :         const char *pszWKT =
    6032             :             "GEODCRS[\"WGS 84\","
    6033             :             "DATUM[\"World Geodetic System 1984\","
    6034             :             "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
    6035             :             "LENGTHUNIT[\"metre\",1.0]]],"
    6036             :             "CS[ellipsoidal,3],"
    6037             :             "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
    6038             :             "0.0174532925199433]],"
    6039             :             "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
    6040             :             "0.0174532925199433]],"
    6041             :             "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
    6042             :             "LENGTHUNIT[\"metre\",1.0]],"
    6043             :             "ID[\"EPSG\",4979]]";
    6044             : 
    6045          39 :         pszSQL = sqlite3_mprintf(
    6046             :             "INSERT INTO gpkg_spatial_ref_sys "
    6047             :             "(srs_name,srs_id,organization,organization_coordsys_id,"
    6048             :             "definition,definition_12_063) VALUES "
    6049             :             "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
    6050             :             pszWKT);
    6051          39 :         osSQL += ";";
    6052          39 :         osSQL += pszSQL;
    6053          39 :         sqlite3_free(pszSQL);
    6054             :     }
    6055             : 
    6056          39 :     return SQLCommand(hDB, osSQL) == OGRERR_NONE;
    6057             : }
    6058             : 
    6059             : /************************************************************************/
    6060             : /*                    HasGriddedCoverageAncillaryTable()                */
    6061             : /************************************************************************/
    6062             : 
    6063          41 : bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
    6064             : {
    6065             :     auto oResultTable = SQLQuery(
    6066             :         hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
    6067          41 :              "name = 'gpkg_2d_gridded_coverage_ancillary'");
    6068          41 :     bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
    6069          82 :     return bHasTable;
    6070             : }
    6071             : 
    6072             : /************************************************************************/
    6073             : /*                      GetUnderlyingDataset()                          */
    6074             : /************************************************************************/
    6075             : 
    6076           3 : static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
    6077             : {
    6078           3 :     if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
    6079             :     {
    6080           0 :         auto poTmpDS = poVRTDS->GetSingleSimpleSource();
    6081           0 :         if (poTmpDS)
    6082           0 :             return poTmpDS;
    6083             :     }
    6084             : 
    6085           3 :     return poSrcDS;
    6086             : }
    6087             : 
    6088             : /************************************************************************/
    6089             : /*                            CreateCopy()                              */
    6090             : /************************************************************************/
    6091             : 
    6092             : typedef struct
    6093             : {
    6094             :     const char *pszName;
    6095             :     GDALResampleAlg eResampleAlg;
    6096             : } WarpResamplingAlg;
    6097             : 
    6098             : static const WarpResamplingAlg asResamplingAlg[] = {
    6099             :     {"NEAREST", GRA_NearestNeighbour},
    6100             :     {"BILINEAR", GRA_Bilinear},
    6101             :     {"CUBIC", GRA_Cubic},
    6102             :     {"CUBICSPLINE", GRA_CubicSpline},
    6103             :     {"LANCZOS", GRA_Lanczos},
    6104             :     {"MODE", GRA_Mode},
    6105             :     {"AVERAGE", GRA_Average},
    6106             :     {"RMS", GRA_RMS},
    6107             : };
    6108             : 
    6109         137 : GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
    6110             :                                                GDALDataset *poSrcDS,
    6111             :                                                int bStrict, char **papszOptions,
    6112             :                                                GDALProgressFunc pfnProgress,
    6113             :                                                void *pProgressData)
    6114             : {
    6115             :     const char *pszTilingScheme =
    6116         137 :         CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
    6117             : 
    6118         274 :     CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
    6119         137 :     if (CPLTestBool(
    6120         142 :             CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
    6121           5 :         CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
    6122             :     {
    6123             :         CPLString osBasename(
    6124           6 :             CPLGetBasename(GetUnderlyingDataset(poSrcDS)->GetDescription()));
    6125           3 :         apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename);
    6126             :     }
    6127             : 
    6128         137 :     const int nBands = poSrcDS->GetRasterCount();
    6129         137 :     if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
    6130             :     {
    6131           2 :         CPLError(CE_Failure, CPLE_NotSupported,
    6132             :                  "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
    6133             :                  "4 (RGBA) band dataset supported");
    6134           2 :         return nullptr;
    6135             :     }
    6136             : 
    6137         135 :     const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
    6138         270 :     if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
    6139         135 :         !EQUAL(pszUnitType, ""))
    6140             :     {
    6141           1 :         apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
    6142             :     }
    6143             : 
    6144         135 :     if (EQUAL(pszTilingScheme, "CUSTOM"))
    6145             :     {
    6146         111 :         if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
    6147             :         {
    6148           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6149             :                      "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
    6150           0 :             return nullptr;
    6151             :         }
    6152             : 
    6153         111 :         GDALGeoPackageDataset *poDS = nullptr;
    6154             :         GDALDriver *poThisDriver =
    6155         111 :             reinterpret_cast<GDALDriver *>(GDALGetDriverByName("GPKG"));
    6156         111 :         if (poThisDriver != nullptr)
    6157             :         {
    6158         111 :             poDS = cpl::down_cast<GDALGeoPackageDataset *>(
    6159             :                 poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
    6160             :                                                 apszUpdatedOptions, pfnProgress,
    6161         111 :                                                 pProgressData));
    6162             : 
    6163         205 :             if (poDS != nullptr &&
    6164         111 :                 poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
    6165             :                 nBands <= 3)
    6166             :             {
    6167          59 :                 poDS->m_nBandCountFromMetadata = nBands;
    6168          59 :                 poDS->m_bMetadataDirty = true;
    6169             :             }
    6170             :         }
    6171         111 :         if (poDS)
    6172          94 :             poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6173         111 :         return poDS;
    6174             :     }
    6175             : 
    6176          48 :     const auto poTS = GetTilingScheme(pszTilingScheme);
    6177          24 :     if (!poTS)
    6178             :     {
    6179           2 :         return nullptr;
    6180             :     }
    6181          22 :     const int nEPSGCode = poTS->nEPSGCode;
    6182             : 
    6183          44 :     OGRSpatialReference oSRS;
    6184          22 :     if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
    6185             :     {
    6186           0 :         return nullptr;
    6187             :     }
    6188          22 :     char *pszWKT = nullptr;
    6189          22 :     oSRS.exportToWkt(&pszWKT);
    6190          22 :     char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
    6191             : 
    6192          22 :     void *hTransformArg = nullptr;
    6193             : 
    6194             :     // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
    6195             :     // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
    6196             :     // EPSG:3857.
    6197             :     double adfSrcGeoTransform[6];
    6198          22 :     std::unique_ptr<GDALDataset> poTmpDS;
    6199          22 :     bool bEPSG3857Adjust = false;
    6200          30 :     if (nEPSGCode == 3857 &&
    6201           8 :         poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
    6202          38 :         adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
    6203           8 :         adfSrcGeoTransform[5] < 0)
    6204             :     {
    6205           8 :         const auto poSrcSRS = poSrcDS->GetSpatialRef();
    6206           8 :         if (poSrcSRS && poSrcSRS->IsGeographic())
    6207             :         {
    6208           2 :             double maxLat = adfSrcGeoTransform[3];
    6209           2 :             double minLat = adfSrcGeoTransform[3] +
    6210           2 :                             poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
    6211             :             // Corresponds to the latitude of below MAX_GM
    6212           2 :             constexpr double MAX_LAT = 85.0511287798066;
    6213           2 :             bool bModified = false;
    6214           2 :             if (maxLat > MAX_LAT)
    6215             :             {
    6216           2 :                 maxLat = MAX_LAT;
    6217           2 :                 bModified = true;
    6218             :             }
    6219           2 :             if (minLat < -MAX_LAT)
    6220             :             {
    6221           2 :                 minLat = -MAX_LAT;
    6222           2 :                 bModified = true;
    6223             :             }
    6224           2 :             if (bModified)
    6225             :             {
    6226           4 :                 CPLStringList aosOptions;
    6227           2 :                 aosOptions.AddString("-of");
    6228           2 :                 aosOptions.AddString("VRT");
    6229           2 :                 aosOptions.AddString("-projwin");
    6230             :                 aosOptions.AddString(
    6231           2 :                     CPLSPrintf("%.18g", adfSrcGeoTransform[0]));
    6232           2 :                 aosOptions.AddString(CPLSPrintf("%.18g", maxLat));
    6233             :                 aosOptions.AddString(
    6234           2 :                     CPLSPrintf("%.18g", adfSrcGeoTransform[0] +
    6235           2 :                                             poSrcDS->GetRasterXSize() *
    6236           2 :                                                 adfSrcGeoTransform[1]));
    6237           2 :                 aosOptions.AddString(CPLSPrintf("%.18g", minLat));
    6238             :                 auto psOptions =
    6239           2 :                     GDALTranslateOptionsNew(aosOptions.List(), nullptr);
    6240           2 :                 poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
    6241             :                     "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
    6242           2 :                 GDALTranslateOptionsFree(psOptions);
    6243           2 :                 if (poTmpDS)
    6244             :                 {
    6245           2 :                     bEPSG3857Adjust = true;
    6246           2 :                     hTransformArg = GDALCreateGenImgProjTransformer2(
    6247           2 :                         GDALDataset::FromHandle(poTmpDS.get()), nullptr,
    6248             :                         papszTO);
    6249             :                 }
    6250             :             }
    6251             :         }
    6252             :     }
    6253          22 :     if (hTransformArg == nullptr)
    6254             :     {
    6255             :         hTransformArg =
    6256          20 :             GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
    6257             :     }
    6258             : 
    6259          22 :     if (hTransformArg == nullptr)
    6260             :     {
    6261           1 :         CPLFree(pszWKT);
    6262           1 :         CSLDestroy(papszTO);
    6263           1 :         return nullptr;
    6264             :     }
    6265             : 
    6266          21 :     GDALTransformerInfo *psInfo =
    6267             :         static_cast<GDALTransformerInfo *>(hTransformArg);
    6268             :     double adfGeoTransform[6];
    6269             :     double adfExtent[4];
    6270             :     int nXSize, nYSize;
    6271             : 
    6272          21 :     if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
    6273             :                                  adfGeoTransform, &nXSize, &nYSize, adfExtent,
    6274          21 :                                  0) != CE_None)
    6275             :     {
    6276           0 :         CPLFree(pszWKT);
    6277           0 :         CSLDestroy(papszTO);
    6278           0 :         GDALDestroyGenImgProjTransformer(hTransformArg);
    6279           0 :         return nullptr;
    6280             :     }
    6281             : 
    6282          21 :     GDALDestroyGenImgProjTransformer(hTransformArg);
    6283          21 :     hTransformArg = nullptr;
    6284          21 :     poTmpDS.reset();
    6285             : 
    6286          21 :     if (bEPSG3857Adjust)
    6287             :     {
    6288           2 :         constexpr double SPHERICAL_RADIUS = 6378137.0;
    6289           2 :         constexpr double MAX_GM =
    6290             :             SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
    6291           2 :         double maxNorthing = adfGeoTransform[3];
    6292           2 :         double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
    6293           2 :         bool bChanged = false;
    6294           2 :         if (maxNorthing > MAX_GM)
    6295             :         {
    6296           2 :             bChanged = true;
    6297           2 :             maxNorthing = MAX_GM;
    6298             :         }
    6299           2 :         if (minNorthing < -MAX_GM)
    6300             :         {
    6301           2 :             bChanged = true;
    6302           2 :             minNorthing = -MAX_GM;
    6303             :         }
    6304           2 :         if (bChanged)
    6305             :         {
    6306           2 :             adfGeoTransform[3] = maxNorthing;
    6307           2 :             nYSize =
    6308           2 :                 int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
    6309           2 :             adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
    6310           2 :             adfExtent[3] = maxNorthing;
    6311             :         }
    6312             :     }
    6313             : 
    6314          21 :     double dfComputedRes = adfGeoTransform[1];
    6315          21 :     double dfPrevRes = 0.0;
    6316          21 :     double dfRes = 0.0;
    6317          21 :     int nZoomLevel = 0;  // Used after for.
    6318          21 :     const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
    6319          21 :     if (pszZoomLevel)
    6320             :     {
    6321           2 :         nZoomLevel = atoi(pszZoomLevel);
    6322             : 
    6323           2 :         int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
    6324           2 :         while ((1 << nMaxZoomLevelForThisTM) >
    6325           4 :                    INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6326           2 :                (1 << nMaxZoomLevelForThisTM) >
    6327           2 :                    INT_MAX / poTS->nTileYCountZoomLevel0)
    6328             :         {
    6329           0 :             --nMaxZoomLevelForThisTM;
    6330             :         }
    6331             : 
    6332           2 :         if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
    6333             :         {
    6334           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6335             :                      "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
    6336             :                      pszZoomLevel, nMaxZoomLevelForThisTM);
    6337           1 :             CPLFree(pszWKT);
    6338           1 :             CSLDestroy(papszTO);
    6339           1 :             return nullptr;
    6340             :         }
    6341             :     }
    6342             :     else
    6343             :     {
    6344         171 :         for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
    6345             :         {
    6346         171 :             dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6347         171 :             if (dfComputedRes > dfRes ||
    6348         152 :                 fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
    6349             :                 break;
    6350         152 :             dfPrevRes = dfRes;
    6351             :         }
    6352          38 :         if (nZoomLevel == MAX_ZOOM_LEVEL ||
    6353          38 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
    6354          19 :             (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
    6355             :         {
    6356           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6357             :                      "Could not find an appropriate zoom level");
    6358           0 :             CPLFree(pszWKT);
    6359           0 :             CSLDestroy(papszTO);
    6360           0 :             return nullptr;
    6361             :         }
    6362             : 
    6363          19 :         if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
    6364             :         {
    6365          17 :             const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
    6366             :                 papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
    6367          17 :             if (EQUAL(pszZoomLevelStrategy, "LOWER"))
    6368             :             {
    6369           1 :                 nZoomLevel--;
    6370             :             }
    6371          16 :             else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
    6372             :             {
    6373             :                 /* do nothing */
    6374             :             }
    6375             :             else
    6376             :             {
    6377          15 :                 if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
    6378          13 :                     nZoomLevel--;
    6379             :             }
    6380             :         }
    6381             :     }
    6382             : 
    6383          20 :     dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
    6384             : 
    6385          20 :     double dfMinX = adfExtent[0];
    6386          20 :     double dfMinY = adfExtent[1];
    6387          20 :     double dfMaxX = adfExtent[2];
    6388          20 :     double dfMaxY = adfExtent[3];
    6389             : 
    6390          20 :     nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
    6391          20 :     nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
    6392          20 :     adfGeoTransform[1] = dfRes;
    6393          20 :     adfGeoTransform[5] = -dfRes;
    6394             : 
    6395          20 :     const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    6396          20 :     int nTargetBands = nBands;
    6397             :     /* For grey level or RGB, if there's reprojection involved, add an alpha */
    6398             :     /* channel */
    6399          37 :     if (eDT == GDT_Byte &&
    6400          13 :         ((nBands == 1 &&
    6401          17 :           poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
    6402             :          nBands == 3))
    6403             :     {
    6404          30 :         OGRSpatialReference oSrcSRS;
    6405          15 :         oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
    6406          15 :         oSrcSRS.AutoIdentifyEPSG();
    6407          30 :         if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
    6408          15 :             atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
    6409             :         {
    6410          13 :             nTargetBands++;
    6411             :         }
    6412             :     }
    6413             : 
    6414          20 :     GDALResampleAlg eResampleAlg = GRA_Bilinear;
    6415          20 :     const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
    6416          20 :     if (pszResampling)
    6417             :     {
    6418           6 :         for (size_t iAlg = 0;
    6419           6 :              iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
    6420             :              iAlg++)
    6421             :         {
    6422           6 :             if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
    6423             :             {
    6424           3 :                 eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
    6425           3 :                 break;
    6426             :             }
    6427             :         }
    6428             :     }
    6429             : 
    6430          16 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    6431          36 :         eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
    6432             :     {
    6433           0 :         CPLError(
    6434             :             CE_Warning, CPLE_AppDefined,
    6435             :             "Input dataset has a color table, which will likely lead to "
    6436             :             "bad results when using a resampling method other than "
    6437             :             "nearest neighbour or mode. Converting the dataset to 24/32 bit "
    6438             :             "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
    6439             :     }
    6440             : 
    6441          20 :     GDALGeoPackageDataset *poDS = new GDALGeoPackageDataset();
    6442          20 :     if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
    6443             :                        apszUpdatedOptions)))
    6444             :     {
    6445           1 :         delete poDS;
    6446           1 :         CPLFree(pszWKT);
    6447           1 :         CSLDestroy(papszTO);
    6448           1 :         return nullptr;
    6449             :     }
    6450             : 
    6451             :     // Assign nodata values before the SetGeoTransform call.
    6452             :     // SetGeoTransform will trigger creation of the overview datasets for each
    6453             :     // zoom level and at that point the nodata value needs to be known.
    6454          19 :     int bHasNoData = FALSE;
    6455             :     double dfNoDataValue =
    6456          19 :         poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    6457          19 :     if (eDT != GDT_Byte && bHasNoData)
    6458             :     {
    6459           3 :         poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
    6460             :     }
    6461             : 
    6462          19 :     poDS->SetGeoTransform(adfGeoTransform);
    6463          19 :     poDS->SetProjection(pszWKT);
    6464          19 :     CPLFree(pszWKT);
    6465          19 :     pszWKT = nullptr;
    6466          24 :     if (nTargetBands == 1 && nBands == 1 &&
    6467           5 :         poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
    6468             :     {
    6469           2 :         poDS->GetRasterBand(1)->SetColorTable(
    6470           1 :             poSrcDS->GetRasterBand(1)->GetColorTable());
    6471             :     }
    6472             : 
    6473          19 :     hTransformArg = GDALCreateGenImgProjTransformer2(poSrcDS, poDS, papszTO);
    6474          19 :     CSLDestroy(papszTO);
    6475          19 :     if (hTransformArg == nullptr)
    6476             :     {
    6477           0 :         delete poDS;
    6478           0 :         return nullptr;
    6479             :     }
    6480             : 
    6481          19 :     poDS->SetMetadata(poSrcDS->GetMetadata());
    6482             : 
    6483             :     /* -------------------------------------------------------------------- */
    6484             :     /*      Warp the transformer with a linear approximator                 */
    6485             :     /* -------------------------------------------------------------------- */
    6486          19 :     hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
    6487             :                                                 hTransformArg, 0.125);
    6488          19 :     GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
    6489             : 
    6490             :     /* -------------------------------------------------------------------- */
    6491             :     /*      Setup warp options.                                             */
    6492             :     /* -------------------------------------------------------------------- */
    6493          19 :     GDALWarpOptions *psWO = GDALCreateWarpOptions();
    6494             : 
    6495          19 :     psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
    6496          19 :     psWO->papszWarpOptions =
    6497          19 :         CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
    6498          19 :     if (bHasNoData)
    6499             :     {
    6500           3 :         if (dfNoDataValue == 0.0)
    6501             :         {
    6502             :             // Do not initialize in the case where nodata != 0, since we
    6503             :             // want the GeoPackage driver to return empty tiles at the nodata
    6504             :             // value instead of 0 as GDAL core would
    6505           0 :             psWO->papszWarpOptions =
    6506           0 :                 CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
    6507             :         }
    6508             : 
    6509           3 :         psWO->padfSrcNoDataReal =
    6510           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6511           3 :         psWO->padfSrcNoDataReal[0] = dfNoDataValue;
    6512             : 
    6513           3 :         psWO->padfDstNoDataReal =
    6514           3 :             static_cast<double *>(CPLMalloc(sizeof(double)));
    6515           3 :         psWO->padfDstNoDataReal[0] = dfNoDataValue;
    6516             :     }
    6517          19 :     psWO->eWorkingDataType = eDT;
    6518          19 :     psWO->eResampleAlg = eResampleAlg;
    6519             : 
    6520          19 :     psWO->hSrcDS = poSrcDS;
    6521          19 :     psWO->hDstDS = poDS;
    6522             : 
    6523          19 :     psWO->pfnTransformer = GDALApproxTransform;
    6524          19 :     psWO->pTransformerArg = hTransformArg;
    6525             : 
    6526          19 :     psWO->pfnProgress = pfnProgress;
    6527          19 :     psWO->pProgressArg = pProgressData;
    6528             : 
    6529             :     /* -------------------------------------------------------------------- */
    6530             :     /*      Setup band mapping.                                             */
    6531             :     /* -------------------------------------------------------------------- */
    6532             : 
    6533          19 :     if (nBands == 2 || nBands == 4)
    6534           1 :         psWO->nBandCount = nBands - 1;
    6535             :     else
    6536          18 :         psWO->nBandCount = nBands;
    6537             : 
    6538          19 :     psWO->panSrcBands =
    6539          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6540          19 :     psWO->panDstBands =
    6541          19 :         static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
    6542             : 
    6543          46 :     for (int i = 0; i < psWO->nBandCount; i++)
    6544             :     {
    6545          27 :         psWO->panSrcBands[i] = i + 1;
    6546          27 :         psWO->panDstBands[i] = i + 1;
    6547             :     }
    6548             : 
    6549          19 :     if (nBands == 2 || nBands == 4)
    6550             :     {
    6551           1 :         psWO->nSrcAlphaBand = nBands;
    6552             :     }
    6553          19 :     if (nTargetBands == 2 || nTargetBands == 4)
    6554             :     {
    6555          13 :         psWO->nDstAlphaBand = nTargetBands;
    6556             :     }
    6557             : 
    6558             :     /* -------------------------------------------------------------------- */
    6559             :     /*      Initialize and execute the warp.                                */
    6560             :     /* -------------------------------------------------------------------- */
    6561          19 :     GDALWarpOperation oWO;
    6562             : 
    6563          19 :     CPLErr eErr = oWO.Initialize(psWO);
    6564          19 :     if (eErr == CE_None)
    6565             :     {
    6566             :         /*if( bMulti )
    6567             :             eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
    6568             :         else*/
    6569          19 :         eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
    6570             :     }
    6571          19 :     if (eErr != CE_None)
    6572             :     {
    6573           0 :         delete poDS;
    6574           0 :         poDS = nullptr;
    6575             :     }
    6576             : 
    6577          19 :     GDALDestroyTransformer(hTransformArg);
    6578          19 :     GDALDestroyWarpOptions(psWO);
    6579             : 
    6580          19 :     if (poDS)
    6581          19 :         poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    6582             : 
    6583          19 :     return poDS;
    6584             : }
    6585             : 
    6586             : /************************************************************************/
    6587             : /*                        ParseCompressionOptions()                     */
    6588             : /************************************************************************/
    6589             : 
    6590         416 : void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
    6591             : {
    6592         416 :     const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
    6593         416 :     if (pszZLevel)
    6594           0 :         m_nZLevel = atoi(pszZLevel);
    6595             : 
    6596         416 :     const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
    6597         416 :     if (pszQuality)
    6598           0 :         m_nQuality = atoi(pszQuality);
    6599             : 
    6600         416 :     const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
    6601         416 :     if (pszDither)
    6602           0 :         m_bDither = CPLTestBool(pszDither);
    6603         416 : }
    6604             : 
    6605             : /************************************************************************/
    6606             : /*                          RegisterWebPExtension()                     */
    6607             : /************************************************************************/
    6608             : 
    6609          11 : bool GDALGeoPackageDataset::RegisterWebPExtension()
    6610             : {
    6611          11 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6612           0 :         return false;
    6613             : 
    6614          11 :     char *pszSQL = sqlite3_mprintf(
    6615             :         "INSERT INTO gpkg_extensions "
    6616             :         "(table_name, column_name, extension_name, definition, scope) "
    6617             :         "VALUES "
    6618             :         "('%q', 'tile_data', 'gpkg_webp', "
    6619             :         "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
    6620             :         "'read-write')",
    6621             :         m_osRasterTable.c_str());
    6622          11 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6623          11 :     sqlite3_free(pszSQL);
    6624             : 
    6625          11 :     return OGRERR_NONE == eErr;
    6626             : }
    6627             : 
    6628             : /************************************************************************/
    6629             : /*                       RegisterZoomOtherExtension()                   */
    6630             : /************************************************************************/
    6631             : 
    6632           1 : bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
    6633             : {
    6634           1 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
    6635           0 :         return false;
    6636             : 
    6637           1 :     char *pszSQL = sqlite3_mprintf(
    6638             :         "INSERT INTO gpkg_extensions "
    6639             :         "(table_name, column_name, extension_name, definition, scope) "
    6640             :         "VALUES "
    6641             :         "('%q', 'tile_data', 'gpkg_zoom_other', "
    6642             :         "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
    6643             :         "'read-write')",
    6644             :         m_osRasterTable.c_str());
    6645           1 :     const OGRErr eErr = SQLCommand(hDB, pszSQL);
    6646           1 :     sqlite3_free(pszSQL);
    6647           1 :     return OGRERR_NONE == eErr;
    6648             : }
    6649             : 
    6650             : /************************************************************************/
    6651             : /*                              GetLayer()                              */
    6652             : /************************************************************************/
    6653             : 
    6654       13126 : OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
    6655             : 
    6656             : {
    6657       13126 :     if (iLayer < 0 || iLayer >= m_nLayers)
    6658           6 :         return nullptr;
    6659             :     else
    6660       13120 :         return m_papoLayers[iLayer];
    6661             : }
    6662             : 
    6663             : /************************************************************************/
    6664             : /*                           LaunderName()                              */
    6665             : /************************************************************************/
    6666             : 
    6667             : /** Launder identifiers (table, column names) according to guidance at
    6668             :  * https://www.geopackage.org/guidance/getting-started.html:
    6669             :  * "For maximum interoperability, start your database identifiers (table names,
    6670             :  * column names, etc.) with a lowercase character and only use lowercase
    6671             :  * characters, numbers 0-9, and underscores (_)."
    6672             :  */
    6673             : 
    6674             : /* static */
    6675           5 : std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
    6676             : {
    6677           5 :     char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
    6678          10 :     const std::string osStrASCII(pszASCII);
    6679           5 :     CPLFree(pszASCII);
    6680             : 
    6681          10 :     std::string osRet;
    6682           5 :     osRet.reserve(osStrASCII.size());
    6683             : 
    6684          29 :     for (size_t i = 0; i < osStrASCII.size(); ++i)
    6685             :     {
    6686          24 :         if (osRet.empty())
    6687             :         {
    6688           5 :             if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6689             :             {
    6690           2 :                 osRet += (osStrASCII[i] - 'A' + 'a');
    6691             :             }
    6692           3 :             else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
    6693             :             {
    6694           2 :                 osRet += osStrASCII[i];
    6695             :             }
    6696             :             else
    6697             :             {
    6698           1 :                 continue;
    6699             :             }
    6700             :         }
    6701          19 :         else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
    6702             :         {
    6703          11 :             osRet += (osStrASCII[i] - 'A' + 'a');
    6704             :         }
    6705           9 :         else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
    6706          14 :                  (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
    6707           5 :                  osStrASCII[i] == '_')
    6708             :         {
    6709           7 :             osRet += osStrASCII[i];
    6710             :         }
    6711             :         else
    6712             :         {
    6713           1 :             osRet += '_';
    6714             :         }
    6715             :     }
    6716             : 
    6717           5 :     if (osRet.empty() && !osStrASCII.empty())
    6718           2 :         return LaunderName(std::string("x").append(osStrASCII));
    6719             : 
    6720           4 :     if (osRet != osStr)
    6721             :     {
    6722           3 :         CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
    6723             :                  osRet.c_str());
    6724             :     }
    6725             : 
    6726           4 :     return osRet;
    6727             : }
    6728             : 
    6729             : /************************************************************************/
    6730             : /*                          ICreateLayer()                              */
    6731             : /************************************************************************/
    6732             : 
    6733             : OGRLayer *
    6734         614 : GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
    6735             :                                     const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    6736             :                                     CSLConstList papszOptions)
    6737             : {
    6738             :     /* -------------------------------------------------------------------- */
    6739             :     /*      Verify we are in update mode.                                   */
    6740             :     /* -------------------------------------------------------------------- */
    6741         614 :     if (!GetUpdate())
    6742             :     {
    6743           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    6744             :                  "Data source %s opened read-only.\n"
    6745             :                  "New layer %s cannot be created.\n",
    6746             :                  m_pszFilename, pszLayerName);
    6747             : 
    6748           0 :         return nullptr;
    6749             :     }
    6750             : 
    6751             :     const bool bLaunder =
    6752         614 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
    6753             :     const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
    6754        1842 :                                            : std::string(pszLayerName));
    6755             : 
    6756             :     const auto eGType =
    6757         614 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    6758             :     const auto poSpatialRef =
    6759         614 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    6760             : 
    6761         614 :     if (!m_bHasGPKGGeometryColumns)
    6762             :     {
    6763           1 :         if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
    6764             :         {
    6765           0 :             return nullptr;
    6766             :         }
    6767           1 :         m_bHasGPKGGeometryColumns = true;
    6768             :     }
    6769             : 
    6770             :     // Check identifier unicity
    6771         614 :     const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
    6772         614 :     if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
    6773           0 :         pszIdentifier = nullptr;
    6774         614 :     if (pszIdentifier != nullptr)
    6775             :     {
    6776          13 :         for (int i = 0; i < m_nLayers; ++i)
    6777             :         {
    6778             :             const char *pszOtherIdentifier =
    6779           9 :                 m_papoLayers[i]->GetMetadataItem("IDENTIFIER");
    6780           9 :             if (pszOtherIdentifier == nullptr)
    6781           6 :                 pszOtherIdentifier = m_papoLayers[i]->GetName();
    6782          18 :             if (pszOtherIdentifier != nullptr &&
    6783          12 :                 EQUAL(pszOtherIdentifier, pszIdentifier) &&
    6784           3 :                 !EQUAL(m_papoLayers[i]->GetName(), osTableName.c_str()))
    6785             :             {
    6786           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6787             :                          "Identifier %s is already used by table %s",
    6788           2 :                          pszIdentifier, m_papoLayers[i]->GetName());
    6789           3 :                 return nullptr;
    6790             :             }
    6791             :         }
    6792             : 
    6793             :         // In case there would be table in gpkg_contents not listed as a
    6794             :         // vector layer
    6795           4 :         char *pszSQL = sqlite3_mprintf(
    6796             :             "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
    6797             :             "LIMIT 2",
    6798             :             pszIdentifier);
    6799           4 :         auto oResult = SQLQuery(hDB, pszSQL);
    6800           4 :         sqlite3_free(pszSQL);
    6801           8 :         if (oResult && oResult->RowCount() > 0 &&
    6802           9 :             oResult->GetValue(0, 0) != nullptr &&
    6803           1 :             !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
    6804             :         {
    6805           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    6806             :                      "Identifier %s is already used by table %s", pszIdentifier,
    6807             :                      oResult->GetValue(0, 0));
    6808           1 :             return nullptr;
    6809             :         }
    6810             :     }
    6811             : 
    6812             :     /* Read GEOMETRY_NAME option */
    6813             :     const char *pszGeomColumnName =
    6814         611 :         CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    6815         611 :     if (pszGeomColumnName == nullptr) /* deprecated name */
    6816         577 :         pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
    6817         611 :     if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
    6818             :     {
    6819         537 :         pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
    6820         537 :         if (pszGeomColumnName && pszGeomColumnName[0] == 0)
    6821         534 :             pszGeomColumnName = nullptr;
    6822             :     }
    6823         611 :     if (pszGeomColumnName == nullptr)
    6824         574 :         pszGeomColumnName = "geom";
    6825             :     const bool bGeomNullable =
    6826         611 :         CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
    6827             : 
    6828             :     /* Read FID option */
    6829         611 :     const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
    6830         611 :     if (pszFIDColumnName == nullptr)
    6831         589 :         pszFIDColumnName = "fid";
    6832             : 
    6833         611 :     if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
    6834             :     {
    6835         611 :         if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
    6836             :         {
    6837           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6838             :                      "The primary key (%s) name may not contain special "
    6839             :                      "characters or spaces",
    6840             :                      pszFIDColumnName);
    6841           0 :             return nullptr;
    6842             :         }
    6843             : 
    6844             :         /* Avoiding gpkg prefixes is not an official requirement, but seems wise
    6845             :          */
    6846         611 :         if (STARTS_WITH(osTableName.c_str(), "gpkg"))
    6847             :         {
    6848           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    6849             :                      "The layer name may not begin with 'gpkg' as it is a "
    6850             :                      "reserved geopackage prefix");
    6851           0 :             return nullptr;
    6852             :         }
    6853             : 
    6854             :         /* Preemptively try and avoid sqlite3 syntax errors due to  */
    6855             :         /* illegal characters. */
    6856         611 :         if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
    6857             :             0)
    6858             :         {
    6859           0 :             CPLError(
    6860             :                 CE_Failure, CPLE_AppDefined,
    6861             :                 "The layer name may not contain special characters or spaces");
    6862           0 :             return nullptr;
    6863             :         }
    6864             :     }
    6865             : 
    6866             :     /* Check for any existing layers that already use this name */
    6867         799 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    6868             :     {
    6869         189 :         if (EQUAL(osTableName.c_str(), m_papoLayers[iLayer]->GetName()))
    6870             :         {
    6871             :             const char *pszOverwrite =
    6872           2 :                 CSLFetchNameValue(papszOptions, "OVERWRITE");
    6873           2 :             if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
    6874             :             {
    6875           1 :                 DeleteLayer(iLayer);
    6876             :             }
    6877             :             else
    6878             :             {
    6879           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    6880             :                          "Layer %s already exists, CreateLayer failed.\n"
    6881             :                          "Use the layer creation option OVERWRITE=YES to "
    6882             :                          "replace it.",
    6883             :                          osTableName.c_str());
    6884           1 :                 return nullptr;
    6885             :             }
    6886             :         }
    6887             :     }
    6888             : 
    6889         610 :     if (m_nLayers == 1)
    6890             :     {
    6891             :         // Async RTree building doesn't play well with multiple layer:
    6892             :         // SQLite3 locks being hold for a long time, random failed commits,
    6893             :         // etc.
    6894          64 :         m_papoLayers[0]->FinishOrDisableThreadedRTree();
    6895             :     }
    6896             : 
    6897             :     /* Create a blank layer. */
    6898             :     auto poLayer = std::unique_ptr<OGRGeoPackageTableLayer>(
    6899        1220 :         new OGRGeoPackageTableLayer(this, osTableName.c_str()));
    6900             : 
    6901         610 :     OGRSpatialReference *poSRS = nullptr;
    6902         610 :     if (poSpatialRef)
    6903             :     {
    6904         178 :         poSRS = poSpatialRef->Clone();
    6905         178 :         poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    6906             :     }
    6907        1221 :     poLayer->SetCreationParameters(
    6908             :         eGType,
    6909         611 :         bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
    6910             :         bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
    6911        1220 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
    6912             :                            : OGRGeomCoordinatePrecision(),
    6913         610 :         CPLTestBool(
    6914             :             CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
    6915         610 :         CPLTestBool(CSLFetchNameValueDef(
    6916             :             papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
    6917         611 :         bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
    6918             :         pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
    6919         610 :     if (poSRS)
    6920             :     {
    6921         178 :         poSRS->Release();
    6922             :     }
    6923             : 
    6924         610 :     poLayer->SetLaunder(bLaunder);
    6925             : 
    6926             :     /* Should we create a spatial index ? */
    6927         610 :     const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
    6928         610 :     int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
    6929         610 :     if (eGType != wkbNone && bCreateSpatialIndex)
    6930             :     {
    6931         545 :         poLayer->SetDeferredSpatialIndexCreation(true);
    6932             :     }
    6933             : 
    6934         610 :     poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
    6935         610 :     poLayer->SetTruncateFieldsFlag(
    6936         610 :         CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
    6937         610 :     if (eGType == wkbNone)
    6938             :     {
    6939          43 :         const char *pszASpatialVariant = CSLFetchNameValueDef(
    6940             :             papszOptions, "ASPATIAL_VARIANT",
    6941          43 :             m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
    6942             :                 ? "NOT_REGISTERED"
    6943             :                 : "GPKG_ATTRIBUTES");
    6944          43 :         GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
    6945          43 :         if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
    6946          31 :             eASpatialVariant = GPKG_ATTRIBUTES;
    6947          12 :         else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
    6948             :         {
    6949           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6950             :                      "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
    6951           0 :             return nullptr;
    6952             :         }
    6953          12 :         else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
    6954          12 :             eASpatialVariant = NOT_REGISTERED;
    6955             :         else
    6956             :         {
    6957           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    6958             :                      "Unsupported value for ASPATIAL_VARIANT: %s",
    6959             :                      pszASpatialVariant);
    6960           0 :             return nullptr;
    6961             :         }
    6962          43 :         poLayer->SetASpatialVariant(eASpatialVariant);
    6963             :     }
    6964             : 
    6965             :     const char *pszDateTimePrecision =
    6966         610 :         CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
    6967         610 :     if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
    6968             :     {
    6969           2 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6970             :     }
    6971         608 :     else if (EQUAL(pszDateTimePrecision, "SECOND"))
    6972             :     {
    6973           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6974           0 :             CPLError(
    6975             :                 CE_Warning, CPLE_AppDefined,
    6976             :                 "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
    6977           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
    6978             :     }
    6979         607 :     else if (EQUAL(pszDateTimePrecision, "MINUTE"))
    6980             :     {
    6981           1 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6982           0 :             CPLError(
    6983             :                 CE_Warning, CPLE_AppDefined,
    6984             :                 "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
    6985           1 :         poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
    6986             :     }
    6987         606 :     else if (EQUAL(pszDateTimePrecision, "AUTO"))
    6988             :     {
    6989         605 :         if (m_nUserVersion < GPKG_1_4_VERSION)
    6990         594 :             poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
    6991             :     }
    6992             :     else
    6993             :     {
    6994           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    6995             :                  "Unsupported value for DATETIME_PRECISION: %s",
    6996             :                  pszDateTimePrecision);
    6997           1 :         return nullptr;
    6998             :     }
    6999             : 
    7000             :     // If there was an ogr_empty_table table, we can remove it
    7001             :     // But do it at dataset closing, otherwise locking performance issues
    7002             :     // can arise (probably when transactions are used).
    7003         609 :     m_bRemoveOGREmptyTable = true;
    7004             : 
    7005        1218 :     m_papoLayers = static_cast<OGRGeoPackageTableLayer **>(CPLRealloc(
    7006         609 :         m_papoLayers, sizeof(OGRGeoPackageTableLayer *) * (m_nLayers + 1)));
    7007         609 :     auto poRet = poLayer.release();
    7008         609 :     m_papoLayers[m_nLayers] = poRet;
    7009         609 :     m_nLayers++;
    7010         609 :     return poRet;
    7011             : }
    7012             : 
    7013             : /************************************************************************/
    7014             : /*                          FindLayerIndex()                            */
    7015             : /************************************************************************/
    7016             : 
    7017          20 : int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
    7018             : 
    7019             : {
    7020          34 :     for (int iLayer = 0; iLayer < m_nLayers; iLayer++)
    7021             :     {
    7022          23 :         if (EQUAL(pszLayerName, m_papoLayers[iLayer]->GetName()))
    7023           9 :             return iLayer;
    7024             :     }
    7025          11 :     return -1;
    7026             : }
    7027             : 
    7028             : /************************************************************************/
    7029             : /*                       DeleteLayerCommon()                            */
    7030             : /************************************************************************/
    7031             : 
    7032          36 : OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
    7033             : {
    7034             :     // Temporary remove foreign key checks
    7035             :     const GPKGTemporaryForeignKeyCheckDisabler
    7036          36 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7037             : 
    7038          36 :     char *pszSQL = sqlite3_mprintf(
    7039             :         "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
    7040             :         pszLayerName);
    7041          36 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
    7042          36 :     sqlite3_free(pszSQL);
    7043             : 
    7044          36 :     if (eErr == OGRERR_NONE && HasExtensionsTable())
    7045             :     {
    7046          34 :         pszSQL = sqlite3_mprintf(
    7047             :             "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
    7048             :             pszLayerName);
    7049          34 :         eErr = SQLCommand(hDB, pszSQL);
    7050          34 :         sqlite3_free(pszSQL);
    7051             :     }
    7052             : 
    7053          36 :     if (eErr == OGRERR_NONE && HasMetadataTables())
    7054             :     {
    7055             :         // Delete from gpkg_metadata metadata records that are only referenced
    7056             :         // by the table we are about to drop
    7057           8 :         pszSQL = sqlite3_mprintf(
    7058             :             "DELETE FROM gpkg_metadata WHERE id IN ("
    7059             :             "SELECT DISTINCT md_file_id FROM "
    7060             :             "gpkg_metadata_reference WHERE "
    7061             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    7062             :             "AND id NOT IN ("
    7063             :             "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
    7064             :             "md_file_id IN (SELECT DISTINCT md_file_id FROM "
    7065             :             "gpkg_metadata_reference WHERE "
    7066             :             "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
    7067             :             "AND lower(table_name) <> lower('%q'))",
    7068             :             pszLayerName, pszLayerName, pszLayerName);
    7069           8 :         eErr = SQLCommand(hDB, pszSQL);
    7070           8 :         sqlite3_free(pszSQL);
    7071             : 
    7072           8 :         if (eErr == OGRERR_NONE)
    7073             :         {
    7074             :             pszSQL =
    7075           8 :                 sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
    7076             :                                 "lower(table_name) = lower('%q')",
    7077             :                                 pszLayerName);
    7078           8 :             eErr = SQLCommand(hDB, pszSQL);
    7079           8 :             sqlite3_free(pszSQL);
    7080             :         }
    7081             :     }
    7082             : 
    7083          36 :     if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
    7084             :     {
    7085             :         // Remove reference to potential corresponding mapping table in
    7086             :         // gpkg_extensions
    7087           4 :         pszSQL = sqlite3_mprintf(
    7088             :             "DELETE FROM gpkg_extensions WHERE "
    7089             :             "extension_name IN ('related_tables', "
    7090             :             "'gpkg_related_tables') AND lower(table_name) = "
    7091             :             "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
    7092             :             "lower(base_table_name) = lower('%q') OR "
    7093             :             "lower(related_table_name) = lower('%q') OR "
    7094             :             "lower(mapping_table_name) = lower('%q'))",
    7095             :             pszLayerName, pszLayerName, pszLayerName);
    7096           4 :         eErr = SQLCommand(hDB, pszSQL);
    7097           4 :         sqlite3_free(pszSQL);
    7098             : 
    7099           4 :         if (eErr == OGRERR_NONE)
    7100             :         {
    7101             :             // Remove reference to potential corresponding mapping table in
    7102             :             // gpkgext_relations
    7103             :             pszSQL =
    7104           4 :                 sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
    7105             :                                 "lower(base_table_name) = lower('%q') OR "
    7106             :                                 "lower(related_table_name) = lower('%q') OR "
    7107             :                                 "lower(mapping_table_name) = lower('%q')",
    7108             :                                 pszLayerName, pszLayerName, pszLayerName);
    7109           4 :             eErr = SQLCommand(hDB, pszSQL);
    7110           4 :             sqlite3_free(pszSQL);
    7111             :         }
    7112             : 
    7113           4 :         if (eErr == OGRERR_NONE && HasExtensionsTable())
    7114             :         {
    7115             :             // If there is no longer any mapping table, then completely
    7116             :             // remove any reference to the extension in gpkg_extensions
    7117             :             // as mandated per the related table specification.
    7118             :             OGRErr err;
    7119           4 :             if (SQLGetInteger(hDB,
    7120             :                               "SELECT COUNT(*) FROM gpkg_extensions WHERE "
    7121             :                               "extension_name IN ('related_tables', "
    7122             :                               "'gpkg_related_tables') AND "
    7123             :                               "lower(table_name) != 'gpkgext_relations'",
    7124           4 :                               &err) == 0)
    7125             :             {
    7126           2 :                 eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
    7127             :                                        "extension_name IN ('related_tables', "
    7128             :                                        "'gpkg_related_tables')");
    7129             :             }
    7130             : 
    7131           4 :             ClearCachedRelationships();
    7132             :         }
    7133             :     }
    7134             : 
    7135          36 :     if (eErr == OGRERR_NONE)
    7136             :     {
    7137          36 :         pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
    7138          36 :         eErr = SQLCommand(hDB, pszSQL);
    7139          36 :         sqlite3_free(pszSQL);
    7140             :     }
    7141             : 
    7142             :     // Check foreign key integrity
    7143          36 :     if (eErr == OGRERR_NONE)
    7144             :     {
    7145          36 :         eErr = PragmaCheck("foreign_key_check", "", 0);
    7146             :     }
    7147             : 
    7148          72 :     return eErr;
    7149             : }
    7150             : 
    7151             : /************************************************************************/
    7152             : /*                            DeleteLayer()                             */
    7153             : /************************************************************************/
    7154             : 
    7155          33 : OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
    7156             : {
    7157          33 :     if (!GetUpdate() || iLayer < 0 || iLayer >= m_nLayers)
    7158           2 :         return OGRERR_FAILURE;
    7159             : 
    7160          31 :     m_papoLayers[iLayer]->ResetReading();
    7161          31 :     m_papoLayers[iLayer]->SyncToDisk();
    7162             : 
    7163          62 :     CPLString osLayerName = m_papoLayers[iLayer]->GetName();
    7164             : 
    7165          31 :     CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
    7166             : 
    7167             :     // Temporary remove foreign key checks
    7168             :     const GPKGTemporaryForeignKeyCheckDisabler
    7169          31 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7170             : 
    7171          31 :     OGRErr eErr = SoftStartTransaction();
    7172             : 
    7173          31 :     if (eErr == OGRERR_NONE)
    7174             :     {
    7175          31 :         if (m_papoLayers[iLayer]->HasSpatialIndex())
    7176          28 :             m_papoLayers[iLayer]->DropSpatialIndex();
    7177             : 
    7178             :         char *pszSQL =
    7179          31 :             sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
    7180             :                             "lower(table_name) = lower('%q')",
    7181             :                             osLayerName.c_str());
    7182          31 :         eErr = SQLCommand(hDB, pszSQL);
    7183          31 :         sqlite3_free(pszSQL);
    7184             :     }
    7185             : 
    7186          31 :     if (eErr == OGRERR_NONE && HasDataColumnsTable())
    7187             :     {
    7188           1 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
    7189             :                                        "lower(table_name) = lower('%q')",
    7190             :                                        osLayerName.c_str());
    7191           1 :         eErr = SQLCommand(hDB, pszSQL);
    7192           1 :         sqlite3_free(pszSQL);
    7193             :     }
    7194             : 
    7195             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7196          31 :     if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
    7197             :     {
    7198          31 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
    7199             :                                        "lower(table_name) = lower('%q')",
    7200             :                                        osLayerName.c_str());
    7201          31 :         eErr = SQLCommand(hDB, pszSQL);
    7202          31 :         sqlite3_free(pszSQL);
    7203             :     }
    7204             : #endif
    7205             : 
    7206          31 :     if (eErr == OGRERR_NONE)
    7207             :     {
    7208          31 :         eErr = DeleteLayerCommon(osLayerName.c_str());
    7209             :     }
    7210             : 
    7211          31 :     if (eErr == OGRERR_NONE)
    7212             :     {
    7213          31 :         eErr = SoftCommitTransaction();
    7214          31 :         if (eErr == OGRERR_NONE)
    7215             :         {
    7216             :             /* Delete the layer object and remove the gap in the layers list */
    7217          31 :             delete m_papoLayers[iLayer];
    7218          31 :             memmove(m_papoLayers + iLayer, m_papoLayers + iLayer + 1,
    7219          31 :                     sizeof(void *) * (m_nLayers - iLayer - 1));
    7220          31 :             m_nLayers--;
    7221             :         }
    7222             :     }
    7223             :     else
    7224             :     {
    7225           0 :         SoftRollbackTransaction();
    7226             :     }
    7227             : 
    7228          31 :     return eErr;
    7229             : }
    7230             : 
    7231             : /************************************************************************/
    7232             : /*                       DeleteRasterLayer()                            */
    7233             : /************************************************************************/
    7234             : 
    7235           2 : OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
    7236             : {
    7237             :     // Temporary remove foreign key checks
    7238             :     const GPKGTemporaryForeignKeyCheckDisabler
    7239           2 :         oGPKGTemporaryForeignKeyCheckDisabler(this);
    7240             : 
    7241           2 :     OGRErr eErr = SoftStartTransaction();
    7242             : 
    7243           2 :     if (eErr == OGRERR_NONE)
    7244             :     {
    7245           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
    7246             :                                        "lower(table_name) = lower('%q')",
    7247             :                                        pszLayerName);
    7248           2 :         eErr = SQLCommand(hDB, pszSQL);
    7249           2 :         sqlite3_free(pszSQL);
    7250             :     }
    7251             : 
    7252           2 :     if (eErr == OGRERR_NONE)
    7253             :     {
    7254           2 :         char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
    7255             :                                        "lower(table_name) = lower('%q')",
    7256             :                                        pszLayerName);
    7257           2 :         eErr = SQLCommand(hDB, pszSQL);
    7258           2 :         sqlite3_free(pszSQL);
    7259             :     }
    7260             : 
    7261           2 :     if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
    7262             :     {
    7263             :         char *pszSQL =
    7264           1 :             sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
    7265             :                             "WHERE lower(tile_matrix_set_name) = lower('%q')",
    7266             :                             pszLayerName);
    7267           1 :         eErr = SQLCommand(hDB, pszSQL);
    7268           1 :         sqlite3_free(pszSQL);
    7269             : 
    7270           1 :         if (eErr == OGRERR_NONE)
    7271             :         {
    7272             :             pszSQL =
    7273           1 :                 sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
    7274             :                                 "WHERE lower(tpudt_name) = lower('%q')",
    7275             :                                 pszLayerName);
    7276           1 :             eErr = SQLCommand(hDB, pszSQL);
    7277           1 :             sqlite3_free(pszSQL);
    7278             :         }
    7279             :     }
    7280             : 
    7281           2 :     if (eErr == OGRERR_NONE)
    7282             :     {
    7283           2 :         eErr = DeleteLayerCommon(pszLayerName);
    7284             :     }
    7285             : 
    7286           2 :     if (eErr == OGRERR_NONE)
    7287             :     {
    7288           2 :         eErr = SoftCommitTransaction();
    7289             :     }
    7290             :     else
    7291             :     {
    7292           0 :         SoftRollbackTransaction();
    7293             :     }
    7294             : 
    7295           4 :     return eErr;
    7296             : }
    7297             : 
    7298             : /************************************************************************/
    7299             : /*                    DeleteVectorOrRasterLayer()                       */
    7300             : /************************************************************************/
    7301             : 
    7302          13 : bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
    7303             : {
    7304             : 
    7305          13 :     int idx = FindLayerIndex(pszLayerName);
    7306          13 :     if (idx >= 0)
    7307             :     {
    7308           5 :         DeleteLayer(idx);
    7309           5 :         return true;
    7310             :     }
    7311             : 
    7312             :     char *pszSQL =
    7313           8 :         sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
    7314             :                         "lower(table_name) = lower('%q') "
    7315             :                         "AND data_type IN ('tiles', '2d-gridded-coverage')",
    7316             :                         pszLayerName);
    7317           8 :     bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
    7318           8 :     sqlite3_free(pszSQL);
    7319           8 :     if (bIsRasterTable)
    7320             :     {
    7321           2 :         DeleteRasterLayer(pszLayerName);
    7322           2 :         return true;
    7323             :     }
    7324           6 :     return false;
    7325             : }
    7326             : 
    7327             : /************************************************************************/
    7328             : /*                       TestCapability()                               */
    7329             : /************************************************************************/
    7330             : 
    7331         303 : int GDALGeoPackageDataset::TestCapability(const char *pszCap)
    7332             : {
    7333         303 :     if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
    7334         181 :         EQUAL(pszCap, "RenameLayer"))
    7335             :     {
    7336         122 :         return GetUpdate();
    7337             :     }
    7338         181 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    7339          12 :         return TRUE;
    7340         169 :     else if (EQUAL(pszCap, ODsCMeasuredGeometries))
    7341           8 :         return TRUE;
    7342         161 :     else if (EQUAL(pszCap, ODsCZGeometries))
    7343           8 :         return TRUE;
    7344         153 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
    7345         153 :              EQUAL(pszCap, GDsCAddRelationship) ||
    7346         153 :              EQUAL(pszCap, GDsCDeleteRelationship) ||
    7347         153 :              EQUAL(pszCap, GDsCUpdateRelationship) ||
    7348         153 :              EQUAL(pszCap, ODsCAddFieldDomain))
    7349           1 :         return GetUpdate();
    7350             : 
    7351         152 :     return OGRSQLiteBaseDataSource::TestCapability(pszCap);
    7352             : }
    7353             : 
    7354             : /************************************************************************/
    7355             : /*                       ResetReadingAllLayers()                        */
    7356             : /************************************************************************/
    7357             : 
    7358          44 : void GDALGeoPackageDataset::ResetReadingAllLayers()
    7359             : {
    7360          95 :     for (int i = 0; i < m_nLayers; i++)
    7361             :     {
    7362          51 :         m_papoLayers[i]->ResetReading();
    7363             :     }
    7364          44 : }
    7365             : 
    7366             : /************************************************************************/
    7367             : /*                             ExecuteSQL()                             */
    7368             : /************************************************************************/
    7369             : 
    7370             : static const char *const apszFuncsWithSideEffects[] = {
    7371             :     "CreateSpatialIndex",
    7372             :     "DisableSpatialIndex",
    7373             :     "HasSpatialIndex",
    7374             :     "RegisterGeometryExtension",
    7375             : };
    7376             : 
    7377        5310 : OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
    7378             :                                             OGRGeometry *poSpatialFilter,
    7379             :                                             const char *pszDialect)
    7380             : 
    7381             : {
    7382        5310 :     m_bHasReadMetadataFromStorage = false;
    7383             : 
    7384        5310 :     FlushMetadata();
    7385             : 
    7386        5328 :     while (*pszSQLCommand != '\0' &&
    7387        5328 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    7388          18 :         pszSQLCommand++;
    7389             : 
    7390       10620 :     CPLString osSQLCommand(pszSQLCommand);
    7391        5310 :     if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
    7392          47 :         osSQLCommand.resize(osSQLCommand.size() - 1);
    7393             : 
    7394        5310 :     if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
    7395             :     {
    7396             :         // Some SQL commands will influence the feature count behind our
    7397             :         // back, so disable it in that case.
    7398             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7399             :         const bool bInsertOrDelete =
    7400        5241 :             osSQLCommand.ifind("insert into ") != std::string::npos ||
    7401        2131 :             osSQLCommand.ifind("insert or replace into ") !=
    7402        7372 :                 std::string::npos ||
    7403        2085 :             osSQLCommand.ifind("delete from ") != std::string::npos;
    7404             :         const bool bRollback =
    7405        5241 :             osSQLCommand.ifind("rollback ") != std::string::npos;
    7406             : #endif
    7407             : 
    7408        6748 :         for (int i = 0; i < m_nLayers; i++)
    7409             :         {
    7410        1507 :             if (m_papoLayers[i]->SyncToDisk() != OGRERR_NONE)
    7411           0 :                 return nullptr;
    7412             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    7413        1700 :             if (bRollback || (bInsertOrDelete &&
    7414         193 :                               osSQLCommand.ifind(m_papoLayers[i]->GetName()) !=
    7415             :                                   std::string::npos))
    7416             :             {
    7417         148 :                 m_papoLayers[i]->DisableFeatureCount();
    7418             :             }
    7419             : #endif
    7420             :         }
    7421             :     }
    7422             : 
    7423        5310 :     if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
    7424        5309 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
    7425        5309 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
    7426        5309 :         EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
    7427             :     {
    7428           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
    7429             :     }
    7430        5309 :     else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
    7431        5308 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
    7432        5308 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
    7433        5308 :              EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
    7434             :     {
    7435           1 :         OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
    7436             :     }
    7437             : 
    7438             :     /* -------------------------------------------------------------------- */
    7439             :     /*      DEBUG "SELECT nolock" command.                                  */
    7440             :     /* -------------------------------------------------------------------- */
    7441        5379 :     if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
    7442          69 :         EQUAL(osSQLCommand, "SELECT nolock"))
    7443             :     {
    7444           3 :         return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
    7445             :     }
    7446             : 
    7447             :     /* -------------------------------------------------------------------- */
    7448             :     /*      Special case DELLAYER: command.                                 */
    7449             :     /* -------------------------------------------------------------------- */
    7450        5307 :     if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
    7451             :     {
    7452           4 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
    7453             : 
    7454           4 :         while (*pszLayerName == ' ')
    7455           0 :             pszLayerName++;
    7456             : 
    7457           4 :         if (!DeleteVectorOrRasterLayer(pszLayerName))
    7458             :         {
    7459           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7460             :                      pszLayerName);
    7461             :         }
    7462           4 :         return nullptr;
    7463             :     }
    7464             : 
    7465             :     /* -------------------------------------------------------------------- */
    7466             :     /*      Special case RECOMPUTE EXTENT ON command.                       */
    7467             :     /* -------------------------------------------------------------------- */
    7468        5303 :     if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
    7469             :     {
    7470             :         const char *pszLayerName =
    7471           4 :             osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
    7472             : 
    7473           4 :         while (*pszLayerName == ' ')
    7474           0 :             pszLayerName++;
    7475             : 
    7476           4 :         int idx = FindLayerIndex(pszLayerName);
    7477           4 :         if (idx >= 0)
    7478             :         {
    7479           4 :             m_papoLayers[idx]->RecomputeExtent();
    7480             :         }
    7481             :         else
    7482           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
    7483             :                      pszLayerName);
    7484           4 :         return nullptr;
    7485             :     }
    7486             : 
    7487             :     /* -------------------------------------------------------------------- */
    7488             :     /*      Intercept DROP TABLE                                            */
    7489             :     /* -------------------------------------------------------------------- */
    7490        5299 :     if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
    7491             :     {
    7492           9 :         const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
    7493             : 
    7494           9 :         while (*pszLayerName == ' ')
    7495           0 :             pszLayerName++;
    7496             : 
    7497           9 :         if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
    7498           4 :             return nullptr;
    7499             :     }
    7500             : 
    7501             :     /* -------------------------------------------------------------------- */
    7502             :     /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
    7503             :     /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
    7504             :     /*      and       ALTER TABLE table DROP COLUMN col_name                */
    7505             :     /*                                                                      */
    7506             :     /*      We do this because SQLite mechanisms can't deal with updating   */
    7507             :     /*      literal values in gpkg_ tables that refer to table and column   */
    7508             :     /*      names.                                                          */
    7509             :     /* -------------------------------------------------------------------- */
    7510        5295 :     if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
    7511             :     {
    7512           6 :         char **papszTokens = SQLTokenize(osSQLCommand);
    7513             :         /* ALTER TABLE src_table RENAME TO dst_table */
    7514          10 :         if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
    7515           4 :             EQUAL(papszTokens[4], "TO"))
    7516             :         {
    7517           4 :             const char *pszSrcTableName = papszTokens[2];
    7518           4 :             const char *pszDstTableName = papszTokens[5];
    7519             :             OGRGeoPackageTableLayer *poSrcLayer =
    7520           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7521           4 :                     GetLayerByName(SQLUnescape(pszSrcTableName)));
    7522           4 :             if (poSrcLayer)
    7523             :             {
    7524           4 :                 poSrcLayer->Rename(SQLUnescape(pszDstTableName));
    7525           4 :                 CSLDestroy(papszTokens);
    7526           4 :                 return nullptr;
    7527             :             }
    7528             :         }
    7529             :         /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
    7530           2 :         else if (CSLCount(papszTokens) == 8 &&
    7531           1 :                  EQUAL(papszTokens[3], "RENAME") &&
    7532           3 :                  EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
    7533             :         {
    7534           1 :             const char *pszTableName = papszTokens[2];
    7535           1 :             const char *pszSrcColumn = papszTokens[5];
    7536           1 :             const char *pszDstColumn = papszTokens[7];
    7537             :             OGRGeoPackageTableLayer *poLayer =
    7538           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7539           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7540           1 :             if (poLayer)
    7541             :             {
    7542           2 :                 int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7543           2 :                     SQLUnescape(pszSrcColumn));
    7544           1 :                 if (nSrcFieldIdx >= 0)
    7545             :                 {
    7546             :                     // OFTString or any type will do as we just alter the name
    7547             :                     // so it will be ignored.
    7548           1 :                     OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
    7549           1 :                                             OFTString);
    7550           1 :                     poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
    7551             :                                             ALTER_NAME_FLAG);
    7552           1 :                     CSLDestroy(papszTokens);
    7553           1 :                     return nullptr;
    7554             :                 }
    7555             :             }
    7556             :         }
    7557             :         /* ALTER TABLE table DROP COLUMN col_name */
    7558           2 :         else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
    7559           1 :                  EQUAL(papszTokens[4], "COLUMN"))
    7560             :         {
    7561           1 :             const char *pszTableName = papszTokens[2];
    7562           1 :             const char *pszColumnName = papszTokens[5];
    7563             :             OGRGeoPackageTableLayer *poLayer =
    7564           0 :                 dynamic_cast<OGRGeoPackageTableLayer *>(
    7565           1 :                     GetLayerByName(SQLUnescape(pszTableName)));
    7566           1 :             if (poLayer)
    7567             :             {
    7568           2 :                 int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
    7569           2 :                     SQLUnescape(pszColumnName));
    7570           1 :                 if (nFieldIdx >= 0)
    7571             :                 {
    7572           1 :                     poLayer->DeleteField(nFieldIdx);
    7573           1 :                     CSLDestroy(papszTokens);
    7574           1 :                     return nullptr;
    7575             :                 }
    7576             :             }
    7577             :         }
    7578           0 :         CSLDestroy(papszTokens);
    7579             :     }
    7580             : 
    7581        5289 :     if (EQUAL(osSQLCommand, "VACUUM"))
    7582             :     {
    7583           9 :         ResetReadingAllLayers();
    7584             :     }
    7585             : 
    7586        5289 :     if (EQUAL(osSQLCommand, "BEGIN"))
    7587             :     {
    7588           0 :         SoftStartTransaction();
    7589           0 :         return nullptr;
    7590             :     }
    7591        5289 :     else if (EQUAL(osSQLCommand, "COMMIT"))
    7592             :     {
    7593           0 :         SoftCommitTransaction();
    7594           0 :         return nullptr;
    7595             :     }
    7596        5289 :     else if (EQUAL(osSQLCommand, "ROLLBACK"))
    7597             :     {
    7598           0 :         SoftRollbackTransaction();
    7599           0 :         return nullptr;
    7600             :     }
    7601             : 
    7602        5289 :     else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
    7603           1 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
    7604        5288 :     else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
    7605          66 :              !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
    7606          66 :              !EQUAL(pszDialect, "DEBUG"))
    7607           0 :         return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
    7608           0 :                                        pszDialect);
    7609             : 
    7610             :     /* -------------------------------------------------------------------- */
    7611             :     /*      Prepare statement.                                              */
    7612             :     /* -------------------------------------------------------------------- */
    7613        5288 :     sqlite3_stmt *hSQLStmt = nullptr;
    7614             : 
    7615             :     /* This will speed-up layer creation */
    7616             :     /* ORDER BY are costly to evaluate and are not necessary to establish */
    7617             :     /* the layer definition. */
    7618        5288 :     bool bUseStatementForGetNextFeature = true;
    7619        5288 :     bool bEmptyLayer = false;
    7620       10576 :     CPLString osSQLCommandTruncated(osSQLCommand);
    7621             : 
    7622       17394 :     if (osSQLCommand.ifind("SELECT ") == 0 &&
    7623        6053 :         CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
    7624         731 :             std::string::npos &&
    7625         731 :         osSQLCommand.ifind(" UNION ") == std::string::npos &&
    7626        6784 :         osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
    7627         731 :         osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
    7628             :     {
    7629         731 :         size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
    7630         731 :         if (nOrderByPos != std::string::npos)
    7631             :         {
    7632           5 :             osSQLCommandTruncated.resize(nOrderByPos);
    7633           5 :             bUseStatementForGetNextFeature = false;
    7634             :         }
    7635             :     }
    7636             : 
    7637        5288 :     int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
    7638        5288 :                         static_cast<int>(osSQLCommandTruncated.size()),
    7639             :                         &hSQLStmt, nullptr);
    7640             : 
    7641        5288 :     if (rc != SQLITE_OK)
    7642             :     {
    7643           8 :         CPLError(CE_Failure, CPLE_AppDefined,
    7644             :                  "In ExecuteSQL(): sqlite3_prepare_v2(%s):\n  %s",
    7645             :                  osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7646             : 
    7647           8 :         if (hSQLStmt != nullptr)
    7648             :         {
    7649           0 :             sqlite3_finalize(hSQLStmt);
    7650             :         }
    7651             : 
    7652           8 :         return nullptr;
    7653             :     }
    7654             : 
    7655             :     /* -------------------------------------------------------------------- */
    7656             :     /*      Do we get a resultset?                                          */
    7657             :     /* -------------------------------------------------------------------- */
    7658        5280 :     rc = sqlite3_step(hSQLStmt);
    7659             : 
    7660        6816 :     for (int i = 0; i < m_nLayers; i++)
    7661             :     {
    7662        1536 :         m_papoLayers[i]->RunDeferredDropRTreeTableIfNecessary();
    7663             :     }
    7664             : 
    7665        5280 :     if (rc != SQLITE_ROW)
    7666             :     {
    7667        4595 :         if (rc != SQLITE_DONE)
    7668             :         {
    7669           7 :             CPLError(CE_Failure, CPLE_AppDefined,
    7670             :                      "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
    7671             :                      osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
    7672             : 
    7673           7 :             sqlite3_finalize(hSQLStmt);
    7674           7 :             return nullptr;
    7675             :         }
    7676             : 
    7677        4588 :         if (EQUAL(osSQLCommand, "VACUUM"))
    7678             :         {
    7679           9 :             sqlite3_finalize(hSQLStmt);
    7680             :             /* VACUUM rewrites the DB, so we need to reset the application id */
    7681           9 :             SetApplicationAndUserVersionId();
    7682           9 :             return nullptr;
    7683             :         }
    7684             : 
    7685        4579 :         if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7686             :         {
    7687        4458 :             sqlite3_finalize(hSQLStmt);
    7688        4458 :             return nullptr;
    7689             :         }
    7690             : 
    7691         121 :         bUseStatementForGetNextFeature = false;
    7692         121 :         bEmptyLayer = true;
    7693             :     }
    7694             : 
    7695             :     /* -------------------------------------------------------------------- */
    7696             :     /*      Special case for some functions which must be run               */
    7697             :     /*      only once                                                       */
    7698             :     /* -------------------------------------------------------------------- */
    7699         806 :     if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
    7700             :     {
    7701        3669 :         for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
    7702             :                                          sizeof(apszFuncsWithSideEffects[0]);
    7703             :              i++)
    7704             :         {
    7705        2961 :             if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
    7706             :                        strlen(apszFuncsWithSideEffects[i])))
    7707             :             {
    7708         112 :                 if (sqlite3_column_count(hSQLStmt) == 1 &&
    7709          56 :                     sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7710             :                 {
    7711          56 :                     int ret = sqlite3_column_int(hSQLStmt, 0);
    7712             : 
    7713          56 :                     sqlite3_finalize(hSQLStmt);
    7714             : 
    7715             :                     return new OGRSQLiteSingleFeatureLayer(
    7716          56 :                         apszFuncsWithSideEffects[i], ret);
    7717             :                 }
    7718             :             }
    7719             :         }
    7720             :     }
    7721          42 :     else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
    7722             :     {
    7723          57 :         if (sqlite3_column_count(hSQLStmt) == 1 &&
    7724          15 :             sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
    7725             :         {
    7726          12 :             int ret = sqlite3_column_int(hSQLStmt, 0);
    7727             : 
    7728          12 :             sqlite3_finalize(hSQLStmt);
    7729             : 
    7730          12 :             return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
    7731          12 :                                                    ret);
    7732             :         }
    7733          33 :         else if (sqlite3_column_count(hSQLStmt) == 1 &&
    7734           3 :                  sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
    7735             :         {
    7736             :             const char *pszRet = reinterpret_cast<const char *>(
    7737           3 :                 sqlite3_column_text(hSQLStmt, 0));
    7738             : 
    7739             :             OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
    7740           3 :                 osSQLCommand.c_str() + 7, pszRet);
    7741             : 
    7742           3 :             sqlite3_finalize(hSQLStmt);
    7743             : 
    7744           3 :             return poRet;
    7745             :         }
    7746             :     }
    7747             : 
    7748             :     /* -------------------------------------------------------------------- */
    7749             :     /*      Create layer.                                                   */
    7750             :     /* -------------------------------------------------------------------- */
    7751             : 
    7752             :     OGRLayer *poLayer = new OGRGeoPackageSelectLayer(
    7753             :         this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
    7754         735 :         bEmptyLayer);
    7755             : 
    7756         738 :     if (poSpatialFilter != nullptr &&
    7757           3 :         poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
    7758           3 :         poLayer->SetSpatialFilter(0, poSpatialFilter);
    7759             : 
    7760         735 :     return poLayer;
    7761             : }
    7762             : 
    7763             : /************************************************************************/
    7764             : /*                          ReleaseResultSet()                          */
    7765             : /************************************************************************/
    7766             : 
    7767         761 : void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
    7768             : 
    7769             : {
    7770         761 :     delete poLayer;
    7771         761 : }
    7772             : 
    7773             : /************************************************************************/
    7774             : /*                         HasExtensionsTable()                         */
    7775             : /************************************************************************/
    7776             : 
    7777        5116 : bool GDALGeoPackageDataset::HasExtensionsTable()
    7778             : {
    7779        5116 :     return SQLGetInteger(
    7780             :                hDB,
    7781             :                "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
    7782             :                "AND type IN ('table', 'view')",
    7783        5116 :                nullptr) == 1;
    7784             : }
    7785             : 
    7786             : /************************************************************************/
    7787             : /*                    CheckUnknownExtensions()                          */
    7788             : /************************************************************************/
    7789             : 
    7790        1213 : void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
    7791             : {
    7792        1213 :     if (!HasExtensionsTable())
    7793         191 :         return;
    7794             : 
    7795        1022 :     char *pszSQL = nullptr;
    7796        1022 :     if (!bCheckRasterTable)
    7797         829 :         pszSQL = sqlite3_mprintf(
    7798             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7799             :             "WHERE (table_name IS NULL "
    7800             :             "AND extension_name IS NOT NULL "
    7801             :             "AND definition IS NOT NULL "
    7802             :             "AND scope IS NOT NULL "
    7803             :             "AND extension_name NOT IN ("
    7804             :             "'gdal_aspatial', "
    7805             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7806             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7807             :                                        // 17-066r1 finalization
    7808             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7809             :             "'gpkg_metadata', "
    7810             :             "'gpkg_schema', "
    7811             :             "'gpkg_crs_wkt', "
    7812             :             "'gpkg_crs_wkt_1_1', "
    7813             :             "'related_tables', 'gpkg_related_tables')) "
    7814             : #ifdef WORKAROUND_SQLITE3_BUGS
    7815             :             "OR 0 "
    7816             : #endif
    7817             :             "LIMIT 1000");
    7818             :     else
    7819         193 :         pszSQL = sqlite3_mprintf(
    7820             :             "SELECT extension_name, definition, scope FROM gpkg_extensions "
    7821             :             "WHERE (lower(table_name) = lower('%q') "
    7822             :             "AND extension_name IS NOT NULL "
    7823             :             "AND definition IS NOT NULL "
    7824             :             "AND scope IS NOT NULL "
    7825             :             "AND extension_name NOT IN ("
    7826             :             "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
    7827             :             "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
    7828             :                                        // 17-066r1 finalization
    7829             :             "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
    7830             :             "'gpkg_metadata', "
    7831             :             "'gpkg_schema', "
    7832             :             "'gpkg_crs_wkt', "
    7833             :             "'gpkg_crs_wkt_1_1', "
    7834             :             "'related_tables', 'gpkg_related_tables')) "
    7835             : #ifdef WORKAROUND_SQLITE3_BUGS
    7836             :             "OR 0 "
    7837             : #endif
    7838             :             "LIMIT 1000",
    7839             :             m_osRasterTable.c_str());
    7840             : 
    7841        2044 :     auto oResultTable = SQLQuery(GetDB(), pszSQL);
    7842        1022 :     sqlite3_free(pszSQL);
    7843        1022 :     if (oResultTable && oResultTable->RowCount() > 0)
    7844             :     {
    7845          44 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    7846             :         {
    7847          22 :             const char *pszExtName = oResultTable->GetValue(0, i);
    7848          22 :             const char *pszDefinition = oResultTable->GetValue(1, i);
    7849          22 :             const char *pszScope = oResultTable->GetValue(2, i);
    7850          22 :             if (pszExtName == nullptr || pszDefinition == nullptr ||
    7851             :                 pszScope == nullptr)
    7852             :             {
    7853           0 :                 continue;
    7854             :             }
    7855             : 
    7856          22 :             if (EQUAL(pszExtName, "gpkg_webp"))
    7857             :             {
    7858          16 :                 if (GDALGetDriverByName("WEBP") == nullptr)
    7859             :                 {
    7860           1 :                     CPLError(
    7861             :                         CE_Warning, CPLE_AppDefined,
    7862             :                         "Table %s contains WEBP tiles, but GDAL configured "
    7863             :                         "without WEBP support. Data will be missing",
    7864             :                         m_osRasterTable.c_str());
    7865             :                 }
    7866          16 :                 m_eTF = GPKG_TF_WEBP;
    7867          16 :                 continue;
    7868             :             }
    7869           6 :             if (EQUAL(pszExtName, "gpkg_zoom_other"))
    7870             :             {
    7871           2 :                 m_bZoomOther = true;
    7872           2 :                 continue;
    7873             :             }
    7874             : 
    7875           4 :             if (GetUpdate() && EQUAL(pszScope, "write-only"))
    7876             :             {
    7877           1 :                 CPLError(
    7878             :                     CE_Warning, CPLE_AppDefined,
    7879             :                     "Database relies on the '%s' (%s) extension that should "
    7880             :                     "be implemented for safe write-support, but is not "
    7881             :                     "currently. "
    7882             :                     "Update of that database are strongly discouraged to avoid "
    7883             :                     "corruption.",
    7884             :                     pszExtName, pszDefinition);
    7885             :             }
    7886           3 :             else if (GetUpdate() && EQUAL(pszScope, "read-write"))
    7887             :             {
    7888           1 :                 CPLError(
    7889             :                     CE_Warning, CPLE_AppDefined,
    7890             :                     "Database relies on the '%s' (%s) extension that should "
    7891             :                     "be implemented in order to read/write it safely, but is "
    7892             :                     "not currently. "
    7893             :                     "Some data may be missing while reading that database, and "
    7894             :                     "updates are strongly discouraged.",
    7895             :                     pszExtName, pszDefinition);
    7896             :             }
    7897           2 :             else if (EQUAL(pszScope, "read-write") &&
    7898             :                      // None of the NGA extensions at
    7899             :                      // http://ngageoint.github.io/GeoPackage/docs/extensions/
    7900             :                      // affect read-only scenarios
    7901           1 :                      !STARTS_WITH(pszExtName, "nga_"))
    7902             :             {
    7903           1 :                 CPLError(
    7904             :                     CE_Warning, CPLE_AppDefined,
    7905             :                     "Database relies on the '%s' (%s) extension that should "
    7906             :                     "be implemented in order to read it safely, but is not "
    7907             :                     "currently. "
    7908             :                     "Some data may be missing while reading that database.",
    7909             :                     pszExtName, pszDefinition);
    7910             :             }
    7911             :         }
    7912             :     }
    7913             : }
    7914             : 
    7915             : /************************************************************************/
    7916             : /*                         HasGDALAspatialExtension()                       */
    7917             : /************************************************************************/
    7918             : 
    7919         782 : bool GDALGeoPackageDataset::HasGDALAspatialExtension()
    7920             : {
    7921         782 :     if (!HasExtensionsTable())
    7922          84 :         return false;
    7923             : 
    7924             :     auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
    7925             :                                       "WHERE (extension_name = 'gdal_aspatial' "
    7926             :                                       "AND table_name IS NULL "
    7927             :                                       "AND column_name IS NULL)"
    7928             : #ifdef WORKAROUND_SQLITE3_BUGS
    7929             :                                       " OR 0"
    7930             : #endif
    7931         698 :     );
    7932         698 :     bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
    7933         698 :     return bHasExtension;
    7934             : }
    7935             : 
    7936             : /************************************************************************/
    7937             : /*                  CreateExtensionsTableIfNecessary()                  */
    7938             : /************************************************************************/
    7939             : 
    7940         975 : OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
    7941             : {
    7942             :     /* Check if the table gpkg_extensions exists */
    7943         975 :     if (HasExtensionsTable())
    7944         366 :         return OGRERR_NONE;
    7945             : 
    7946             :     /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
    7947             :     /* in a corresponding row in the gpkg_extensions table. The absence of a */
    7948             :     /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
    7949             :     /* SHALL both indicate the absence of extensions to a GeoPackage. */
    7950         609 :     const char *pszCreateGpkgExtensions =
    7951             :         "CREATE TABLE gpkg_extensions ("
    7952             :         "table_name TEXT,"
    7953             :         "column_name TEXT,"
    7954             :         "extension_name TEXT NOT NULL,"
    7955             :         "definition TEXT NOT NULL,"
    7956             :         "scope TEXT NOT NULL,"
    7957             :         "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
    7958             :         ")";
    7959             : 
    7960         609 :     return SQLCommand(hDB, pszCreateGpkgExtensions);
    7961             : }
    7962             : 
    7963             : /************************************************************************/
    7964             : /*                    OGR_GPKG_Intersects_Spatial_Filter()              */
    7965             : /************************************************************************/
    7966             : 
    7967       23044 : void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
    7968             :                                         sqlite3_value **argv)
    7969             : {
    7970       23044 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    7971             :     {
    7972           0 :         sqlite3_result_int(pContext, 0);
    7973       23043 :         return;
    7974             :     }
    7975             : 
    7976             :     auto poLayer =
    7977       23044 :         static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
    7978             : 
    7979       23044 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    7980             :     const GByte *pabyBLOB =
    7981       23044 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    7982             : 
    7983             :     GPkgHeader sHeader;
    7984       46088 :     if (poLayer->m_bFilterIsEnvelope &&
    7985       23044 :         OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
    7986             :     {
    7987       23044 :         if (sHeader.bExtentHasXY)
    7988             :         {
    7989           4 :             OGREnvelope sEnvelope;
    7990           4 :             sEnvelope.MinX = sHeader.MinX;
    7991           4 :             sEnvelope.MinY = sHeader.MinY;
    7992           4 :             sEnvelope.MaxX = sHeader.MaxX;
    7993           4 :             sEnvelope.MaxY = sHeader.MaxY;
    7994           4 :             if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
    7995             :             {
    7996           2 :                 sqlite3_result_int(pContext, 1);
    7997           2 :                 return;
    7998             :             }
    7999             :         }
    8000             : 
    8001             :         // Check if at least one point falls into the layer filter envelope
    8002             :         // nHeaderLen is > 0 for GeoPackage geometries
    8003       46084 :         if (sHeader.nHeaderLen > 0 &&
    8004       23042 :             OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
    8005       23042 :                                         nBLOBLen - sHeader.nHeaderLen,
    8006       23042 :                                         poLayer->m_sFilterEnvelope))
    8007             :         {
    8008       23041 :             sqlite3_result_int(pContext, 1);
    8009       23041 :             return;
    8010             :         }
    8011             :     }
    8012             : 
    8013             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8014           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8015           1 :     if (poGeom == nullptr)
    8016             :     {
    8017             :         // Try also spatialite geometry blobs
    8018           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8019           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8020           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8021             :         {
    8022           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8023           0 :             sqlite3_result_int(pContext, 0);
    8024           0 :             return;
    8025             :         }
    8026           0 :         poGeom.reset(poGeomSpatialite);
    8027             :     }
    8028             : 
    8029           1 :     sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
    8030             : }
    8031             : 
    8032             : /************************************************************************/
    8033             : /*                      OGRGeoPackageSTMinX()                           */
    8034             : /************************************************************************/
    8035             : 
    8036      242083 : static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
    8037             :                                 sqlite3_value **argv)
    8038             : {
    8039             :     GPkgHeader sHeader;
    8040      242083 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8041             :     {
    8042           3 :         sqlite3_result_null(pContext);
    8043           3 :         return;
    8044             :     }
    8045      242080 :     sqlite3_result_double(pContext, sHeader.MinX);
    8046             : }
    8047             : 
    8048             : /************************************************************************/
    8049             : /*                      OGRGeoPackageSTMinY()                           */
    8050             : /************************************************************************/
    8051             : 
    8052      242081 : static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
    8053             :                                 sqlite3_value **argv)
    8054             : {
    8055             :     GPkgHeader sHeader;
    8056      242081 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8057             :     {
    8058           1 :         sqlite3_result_null(pContext);
    8059           1 :         return;
    8060             :     }
    8061      242080 :     sqlite3_result_double(pContext, sHeader.MinY);
    8062             : }
    8063             : 
    8064             : /************************************************************************/
    8065             : /*                      OGRGeoPackageSTMaxX()                           */
    8066             : /************************************************************************/
    8067             : 
    8068      242081 : static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
    8069             :                                 sqlite3_value **argv)
    8070             : {
    8071             :     GPkgHeader sHeader;
    8072      242081 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8073             :     {
    8074           1 :         sqlite3_result_null(pContext);
    8075           1 :         return;
    8076             :     }
    8077      242080 :     sqlite3_result_double(pContext, sHeader.MaxX);
    8078             : }
    8079             : 
    8080             : /************************************************************************/
    8081             : /*                      OGRGeoPackageSTMaxY()                           */
    8082             : /************************************************************************/
    8083             : 
    8084      242081 : static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
    8085             :                                 sqlite3_value **argv)
    8086             : {
    8087             :     GPkgHeader sHeader;
    8088      242081 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8089             :     {
    8090           1 :         sqlite3_result_null(pContext);
    8091           1 :         return;
    8092             :     }
    8093      242080 :     sqlite3_result_double(pContext, sHeader.MaxY);
    8094             : }
    8095             : 
    8096             : /************************************************************************/
    8097             : /*                     OGRGeoPackageSTIsEmpty()                         */
    8098             : /************************************************************************/
    8099             : 
    8100      243303 : static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
    8101             :                                    sqlite3_value **argv)
    8102             : {
    8103             :     GPkgHeader sHeader;
    8104      243303 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8105             :     {
    8106           2 :         sqlite3_result_null(pContext);
    8107           2 :         return;
    8108             :     }
    8109      243301 :     sqlite3_result_int(pContext, sHeader.bEmpty);
    8110             : }
    8111             : 
    8112             : /************************************************************************/
    8113             : /*                    OGRGeoPackageSTGeometryType()                     */
    8114             : /************************************************************************/
    8115             : 
    8116           7 : static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
    8117             :                                         sqlite3_value **argv)
    8118             : {
    8119             :     GPkgHeader sHeader;
    8120             : 
    8121           7 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8122             :     const GByte *pabyBLOB =
    8123           7 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8124             :     OGRwkbGeometryType eGeometryType;
    8125             : 
    8126          13 :     if (nBLOBLen < 8 ||
    8127           6 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8128             :     {
    8129           2 :         if (OGRSQLiteGetSpatialiteGeometryHeader(
    8130             :                 pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
    8131           2 :                 nullptr, nullptr, nullptr) == OGRERR_NONE)
    8132             :         {
    8133           1 :             sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8134             :                                 SQLITE_TRANSIENT);
    8135           4 :             return;
    8136             :         }
    8137             :         else
    8138             :         {
    8139           1 :             sqlite3_result_null(pContext);
    8140           1 :             return;
    8141             :         }
    8142             :     }
    8143             : 
    8144           5 :     if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
    8145             :     {
    8146           2 :         sqlite3_result_null(pContext);
    8147           2 :         return;
    8148             :     }
    8149             : 
    8150           3 :     OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
    8151             :                                         wkbVariantIso, &eGeometryType);
    8152           3 :     if (err != OGRERR_NONE)
    8153           1 :         sqlite3_result_null(pContext);
    8154             :     else
    8155           2 :         sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
    8156             :                             SQLITE_TRANSIENT);
    8157             : }
    8158             : 
    8159             : /************************************************************************/
    8160             : /*                 OGRGeoPackageSTEnvelopesIntersects()                 */
    8161             : /************************************************************************/
    8162             : 
    8163         358 : static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
    8164             :                                                int argc, sqlite3_value **argv)
    8165             : {
    8166             :     GPkgHeader sHeader;
    8167         358 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
    8168             :     {
    8169           2 :         sqlite3_result_int(pContext, FALSE);
    8170         197 :         return;
    8171             :     }
    8172         356 :     const double dfMinX = sqlite3_value_double(argv[1]);
    8173         356 :     if (sHeader.MaxX < dfMinX)
    8174             :     {
    8175         105 :         sqlite3_result_int(pContext, FALSE);
    8176         105 :         return;
    8177             :     }
    8178         251 :     const double dfMinY = sqlite3_value_double(argv[2]);
    8179         251 :     if (sHeader.MaxY < dfMinY)
    8180             :     {
    8181          23 :         sqlite3_result_int(pContext, FALSE);
    8182          23 :         return;
    8183             :     }
    8184         228 :     const double dfMaxX = sqlite3_value_double(argv[3]);
    8185         228 :     if (sHeader.MinX > dfMaxX)
    8186             :     {
    8187          67 :         sqlite3_result_int(pContext, FALSE);
    8188          67 :         return;
    8189             :     }
    8190         161 :     const double dfMaxY = sqlite3_value_double(argv[4]);
    8191         161 :     sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
    8192             : }
    8193             : 
    8194             : /************************************************************************/
    8195             : /*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
    8196             : /************************************************************************/
    8197             : 
    8198             : static void
    8199           3 : OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
    8200             :                                             sqlite3_value **argv)
    8201             : {
    8202             :     GPkgHeader sHeader;
    8203           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
    8204             :     {
    8205           0 :         sqlite3_result_int(pContext, FALSE);
    8206           2 :         return;
    8207             :     }
    8208             :     GPkgHeader sHeader2;
    8209           3 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
    8210             :                                 1))
    8211             :     {
    8212           0 :         sqlite3_result_int(pContext, FALSE);
    8213           0 :         return;
    8214             :     }
    8215           3 :     if (sHeader.MaxX < sHeader2.MinX)
    8216             :     {
    8217           1 :         sqlite3_result_int(pContext, FALSE);
    8218           1 :         return;
    8219             :     }
    8220           2 :     if (sHeader.MaxY < sHeader2.MinY)
    8221             :     {
    8222           0 :         sqlite3_result_int(pContext, FALSE);
    8223           0 :         return;
    8224             :     }
    8225           2 :     if (sHeader.MinX > sHeader2.MaxX)
    8226             :     {
    8227           1 :         sqlite3_result_int(pContext, FALSE);
    8228           1 :         return;
    8229             :     }
    8230           1 :     sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
    8231             : }
    8232             : 
    8233             : /************************************************************************/
    8234             : /*                    OGRGeoPackageGPKGIsAssignable()                   */
    8235             : /************************************************************************/
    8236             : 
    8237           8 : static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
    8238             :                                           int /*argc*/, sqlite3_value **argv)
    8239             : {
    8240          15 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8241           7 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8242             :     {
    8243           2 :         sqlite3_result_int(pContext, 0);
    8244           2 :         return;
    8245             :     }
    8246             : 
    8247             :     const char *pszExpected =
    8248           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8249             :     const char *pszActual =
    8250           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8251           6 :     int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
    8252             :                                             OGRFromOGCGeomType(pszExpected));
    8253           6 :     sqlite3_result_int(pContext, bIsAssignable);
    8254             : }
    8255             : 
    8256             : /************************************************************************/
    8257             : /*                     OGRGeoPackageSTSRID()                            */
    8258             : /************************************************************************/
    8259             : 
    8260          12 : static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
    8261             :                                 sqlite3_value **argv)
    8262             : {
    8263             :     GPkgHeader sHeader;
    8264          12 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8265             :     {
    8266           2 :         sqlite3_result_null(pContext);
    8267           2 :         return;
    8268             :     }
    8269          10 :     sqlite3_result_int(pContext, sHeader.iSrsId);
    8270             : }
    8271             : 
    8272             : /************************************************************************/
    8273             : /*                     OGRGeoPackageSetSRID()                           */
    8274             : /************************************************************************/
    8275             : 
    8276          27 : static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
    8277             :                                  sqlite3_value **argv)
    8278             : {
    8279          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8280             :     {
    8281           1 :         sqlite3_result_null(pContext);
    8282           1 :         return;
    8283             :     }
    8284          26 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8285             :     GPkgHeader sHeader;
    8286          26 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8287             :     const GByte *pabyBLOB =
    8288          26 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8289             : 
    8290          52 :     if (nBLOBLen < 8 ||
    8291          26 :         GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
    8292             :     {
    8293             :         // Try also spatialite geometry blobs
    8294           0 :         OGRGeometry *poGeom = nullptr;
    8295           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
    8296             :             OGRERR_NONE)
    8297             :         {
    8298           0 :             sqlite3_result_null(pContext);
    8299           0 :             return;
    8300             :         }
    8301           0 :         size_t nBLOBDestLen = 0;
    8302             :         GByte *pabyDestBLOB =
    8303           0 :             GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
    8304           0 :         if (!pabyDestBLOB)
    8305             :         {
    8306           0 :             sqlite3_result_null(pContext);
    8307           0 :             return;
    8308             :         }
    8309           0 :         sqlite3_result_blob(pContext, pabyDestBLOB,
    8310             :                             static_cast<int>(nBLOBDestLen), VSIFree);
    8311           0 :         return;
    8312             :     }
    8313             : 
    8314          26 :     GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
    8315          26 :     memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
    8316          26 :     int32_t nSRIDToSerialize = nDestSRID;
    8317          26 :     if (OGR_SWAP(sHeader.eByteOrder))
    8318           0 :         nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
    8319          26 :     memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
    8320          26 :     sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
    8321             : }
    8322             : 
    8323             : /************************************************************************/
    8324             : /*                   OGRGeoPackageSTMakeValid()                         */
    8325             : /************************************************************************/
    8326             : 
    8327           3 : static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
    8328             :                                      sqlite3_value **argv)
    8329             : {
    8330           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8331             :     {
    8332           2 :         sqlite3_result_null(pContext);
    8333           2 :         return;
    8334             :     }
    8335           1 :     int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8336             :     const GByte *pabyBLOB =
    8337           1 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8338             : 
    8339             :     GPkgHeader sHeader;
    8340           1 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8341             :     {
    8342           0 :         sqlite3_result_null(pContext);
    8343           0 :         return;
    8344             :     }
    8345             : 
    8346             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8347           1 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8348           1 :     if (poGeom == nullptr)
    8349             :     {
    8350             :         // Try also spatialite geometry blobs
    8351           0 :         OGRGeometry *poGeomPtr = nullptr;
    8352           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8353             :             OGRERR_NONE)
    8354             :         {
    8355           0 :             sqlite3_result_null(pContext);
    8356           0 :             return;
    8357             :         }
    8358           0 :         poGeom.reset(poGeomPtr);
    8359             :     }
    8360           1 :     auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
    8361           1 :     if (poValid == nullptr)
    8362             :     {
    8363           0 :         sqlite3_result_null(pContext);
    8364           0 :         return;
    8365             :     }
    8366             : 
    8367           1 :     size_t nBLOBDestLen = 0;
    8368           1 :     GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
    8369             :                                               nullptr, &nBLOBDestLen);
    8370           1 :     if (!pabyDestBLOB)
    8371             :     {
    8372           0 :         sqlite3_result_null(pContext);
    8373           0 :         return;
    8374             :     }
    8375           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8376             :                         VSIFree);
    8377             : }
    8378             : 
    8379             : /************************************************************************/
    8380             : /*                   OGRGeoPackageSTArea()                              */
    8381             : /************************************************************************/
    8382             : 
    8383          19 : static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
    8384             :                                 sqlite3_value **argv)
    8385             : {
    8386          19 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8387             :     {
    8388           1 :         sqlite3_result_null(pContext);
    8389          15 :         return;
    8390             :     }
    8391          18 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8392             :     const GByte *pabyBLOB =
    8393          18 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8394             : 
    8395             :     GPkgHeader sHeader;
    8396           0 :     std::unique_ptr<OGRGeometry> poGeom;
    8397          18 :     if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
    8398             :     {
    8399          16 :         if (sHeader.bEmpty)
    8400             :         {
    8401           3 :             sqlite3_result_double(pContext, 0);
    8402          13 :             return;
    8403             :         }
    8404          13 :         const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
    8405          13 :         size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
    8406             :         bool bNeedSwap;
    8407             :         uint32_t nType;
    8408          13 :         if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
    8409             :         {
    8410          13 :             if (nType == wkbPolygon || nType == wkbPolygon25D ||
    8411          11 :                 nType == wkbPolygon + 1000 ||  // wkbPolygonZ
    8412          10 :                 nType == wkbPolygonM || nType == wkbPolygonZM)
    8413             :             {
    8414             :                 double dfArea;
    8415           5 :                 if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8416             :                 {
    8417           5 :                     sqlite3_result_double(pContext, dfArea);
    8418           5 :                     return;
    8419           0 :                 }
    8420             :             }
    8421           8 :             else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
    8422           6 :                      nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
    8423           5 :                      nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
    8424             :             {
    8425             :                 double dfArea;
    8426           5 :                 if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
    8427             :                 {
    8428           5 :                     sqlite3_result_double(pContext, dfArea);
    8429           5 :                     return;
    8430             :                 }
    8431             :             }
    8432             :         }
    8433             : 
    8434             :         // For curve geometries, fallback to OGRGeometry methods
    8435           3 :         poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8436             :     }
    8437             :     else
    8438             :     {
    8439             :         // Try also spatialite geometry blobs
    8440           2 :         OGRGeometry *poGeomPtr = nullptr;
    8441           2 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
    8442             :             OGRERR_NONE)
    8443             :         {
    8444           1 :             sqlite3_result_null(pContext);
    8445           1 :             return;
    8446             :         }
    8447           1 :         poGeom.reset(poGeomPtr);
    8448             :     }
    8449           4 :     auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
    8450           4 :     if (poSurface == nullptr)
    8451             :     {
    8452           2 :         auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
    8453           2 :         if (poMultiSurface == nullptr)
    8454             :         {
    8455           1 :             sqlite3_result_double(pContext, 0);
    8456             :         }
    8457             :         else
    8458             :         {
    8459           1 :             sqlite3_result_double(pContext, poMultiSurface->get_Area());
    8460             :         }
    8461             :     }
    8462             :     else
    8463             :     {
    8464           2 :         sqlite3_result_double(pContext, poSurface->get_Area());
    8465             :     }
    8466             : }
    8467             : 
    8468             : /************************************************************************/
    8469             : /*                     OGRGeoPackageGeodesicArea()                      */
    8470             : /************************************************************************/
    8471             : 
    8472           5 : static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
    8473             :                                       sqlite3_value **argv)
    8474             : {
    8475           5 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8476             :     {
    8477           1 :         sqlite3_result_null(pContext);
    8478           3 :         return;
    8479             :     }
    8480           4 :     if (sqlite3_value_int(argv[1]) != 1)
    8481             :     {
    8482           2 :         CPLError(CE_Warning, CPLE_NotSupported,
    8483             :                  "ST_Area(geom, use_ellipsoid) is only supported for "
    8484             :                  "use_ellipsoid = 1");
    8485             :     }
    8486             : 
    8487           4 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8488             :     const GByte *pabyBLOB =
    8489           4 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8490             :     GPkgHeader sHeader;
    8491           4 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8492             :     {
    8493           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8494           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8495           1 :         return;
    8496             :     }
    8497             : 
    8498             :     GDALGeoPackageDataset *poDS =
    8499           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8500             : 
    8501           3 :     OGRSpatialReference *poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
    8502           3 :     if (poSrcSRS == nullptr)
    8503             :     {
    8504           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    8505             :                  "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8506           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8507           1 :         return;
    8508             :     }
    8509             : 
    8510             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8511           2 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8512           2 :     if (poGeom == nullptr)
    8513             :     {
    8514             :         // Try also spatialite geometry blobs
    8515           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8516           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8517           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8518             :         {
    8519           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8520           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8521           0 :             return;
    8522             :         }
    8523           0 :         poGeom.reset(poGeomSpatialite);
    8524             :     }
    8525             : 
    8526           2 :     poGeom->assignSpatialReference(poSrcSRS);
    8527           2 :     sqlite3_result_double(
    8528             :         pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
    8529             : }
    8530             : 
    8531             : /************************************************************************/
    8532             : /*                      OGRGeoPackageTransform()                        */
    8533             : /************************************************************************/
    8534             : 
    8535             : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8536             :                             sqlite3_value **argv);
    8537             : 
    8538          32 : void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
    8539             :                             sqlite3_value **argv)
    8540             : {
    8541          63 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
    8542          31 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8543             :     {
    8544           2 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8545           6 :         return;
    8546             :     }
    8547             : 
    8548          30 :     const int nBLOBLen = sqlite3_value_bytes(argv[0]);
    8549             :     const GByte *pabyBLOB =
    8550          30 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8551             :     GPkgHeader sHeader;
    8552          30 :     if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
    8553             :     {
    8554           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8555           1 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8556           1 :         return;
    8557             :     }
    8558             : 
    8559          29 :     const int nDestSRID = sqlite3_value_int(argv[1]);
    8560          29 :     if (sHeader.iSrsId == nDestSRID)
    8561             :     {
    8562             :         // Return blob unmodified
    8563           3 :         sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
    8564           3 :         return;
    8565             :     }
    8566             : 
    8567             :     GDALGeoPackageDataset *poDS =
    8568          26 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8569             : 
    8570             :     // Try to get the cached coordinate transformation
    8571             :     OGRCoordinateTransformation *poCT;
    8572          26 :     if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
    8573          20 :         poDS->m_nLastCachedCTDstSRId == nDestSRID)
    8574             :     {
    8575          20 :         poCT = poDS->m_poLastCachedCT.get();
    8576             :     }
    8577             :     else
    8578             :     {
    8579             :         OGRSpatialReference *poSrcSRS =
    8580           6 :             poDS->GetSpatialRef(sHeader.iSrsId, true);
    8581           6 :         if (poSrcSRS == nullptr)
    8582             :         {
    8583           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    8584             :                      "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
    8585           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8586           0 :             return;
    8587             :         }
    8588             : 
    8589           6 :         OGRSpatialReference *poDstSRS = poDS->GetSpatialRef(nDestSRID, true);
    8590           6 :         if (poDstSRS == nullptr)
    8591             :         {
    8592           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
    8593             :                      nDestSRID);
    8594           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8595           0 :             poSrcSRS->Release();
    8596           0 :             return;
    8597             :         }
    8598           6 :         poCT = OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS);
    8599           6 :         poSrcSRS->Release();
    8600           6 :         poDstSRS->Release();
    8601             : 
    8602           6 :         if (poCT == nullptr)
    8603             :         {
    8604           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8605           0 :             return;
    8606             :         }
    8607             : 
    8608             :         // Cache coordinate transformation for potential later reuse
    8609           6 :         poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
    8610           6 :         poDS->m_nLastCachedCTDstSRId = nDestSRID;
    8611           6 :         poDS->m_poLastCachedCT.reset(poCT);
    8612           6 :         poCT = poDS->m_poLastCachedCT.get();
    8613             :     }
    8614             : 
    8615             :     auto poGeom = std::unique_ptr<OGRGeometry>(
    8616          26 :         GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
    8617          26 :     if (poGeom == nullptr)
    8618             :     {
    8619             :         // Try also spatialite geometry blobs
    8620           0 :         OGRGeometry *poGeomSpatialite = nullptr;
    8621           0 :         if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
    8622           0 :                                               &poGeomSpatialite) != OGRERR_NONE)
    8623             :         {
    8624           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
    8625           0 :             sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8626           0 :             return;
    8627             :         }
    8628           0 :         poGeom.reset(poGeomSpatialite);
    8629             :     }
    8630             : 
    8631          26 :     if (poGeom->transform(poCT) != OGRERR_NONE)
    8632             :     {
    8633           0 :         sqlite3_result_blob(pContext, nullptr, 0, nullptr);
    8634           0 :         return;
    8635             :     }
    8636             : 
    8637          26 :     size_t nBLOBDestLen = 0;
    8638             :     GByte *pabyDestBLOB =
    8639          26 :         GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
    8640          26 :     if (!pabyDestBLOB)
    8641             :     {
    8642           0 :         sqlite3_result_null(pContext);
    8643           0 :         return;
    8644             :     }
    8645          26 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    8646             :                         VSIFree);
    8647             : }
    8648             : 
    8649             : /************************************************************************/
    8650             : /*                      OGRGeoPackageSridFromAuthCRS()                  */
    8651             : /************************************************************************/
    8652             : 
    8653           4 : static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
    8654             :                                          int /*argc*/, sqlite3_value **argv)
    8655             : {
    8656           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8657           3 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
    8658             :     {
    8659           2 :         sqlite3_result_int(pContext, -1);
    8660           2 :         return;
    8661             :     }
    8662             : 
    8663             :     GDALGeoPackageDataset *poDS =
    8664           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8665             : 
    8666           2 :     char *pszSQL = sqlite3_mprintf(
    8667             :         "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
    8668             :         "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
    8669           2 :         sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
    8670           2 :     OGRErr err = OGRERR_NONE;
    8671           2 :     int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
    8672           2 :     sqlite3_free(pszSQL);
    8673           2 :     if (err != OGRERR_NONE)
    8674           1 :         nSRSId = -1;
    8675           2 :     sqlite3_result_int(pContext, nSRSId);
    8676             : }
    8677             : 
    8678             : /************************************************************************/
    8679             : /*                    OGRGeoPackageImportFromEPSG()                     */
    8680             : /************************************************************************/
    8681             : 
    8682           4 : static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
    8683             :                                         sqlite3_value **argv)
    8684             : {
    8685           4 :     if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
    8686             :     {
    8687           1 :         sqlite3_result_int(pContext, -1);
    8688           2 :         return;
    8689             :     }
    8690             : 
    8691             :     GDALGeoPackageDataset *poDS =
    8692           3 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8693           3 :     OGRSpatialReference oSRS;
    8694           3 :     if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
    8695             :     {
    8696           1 :         sqlite3_result_int(pContext, -1);
    8697           1 :         return;
    8698             :     }
    8699             : 
    8700           2 :     sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
    8701             : }
    8702             : 
    8703             : /************************************************************************/
    8704             : /*               OGRGeoPackageRegisterGeometryExtension()               */
    8705             : /************************************************************************/
    8706             : 
    8707           1 : static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
    8708             :                                                    int /*argc*/,
    8709             :                                                    sqlite3_value **argv)
    8710             : {
    8711           1 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8712           2 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
    8713           1 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT)
    8714             :     {
    8715           0 :         sqlite3_result_int(pContext, 0);
    8716           0 :         return;
    8717             :     }
    8718             : 
    8719             :     const char *pszTableName =
    8720           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8721             :     const char *pszGeomName =
    8722           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8723             :     const char *pszGeomType =
    8724           1 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    8725             : 
    8726             :     GDALGeoPackageDataset *poDS =
    8727           1 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8728             : 
    8729           1 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8730           1 :         poDS->GetLayerByName(pszTableName));
    8731           1 :     if (poLyr == nullptr)
    8732             :     {
    8733           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8734           0 :         sqlite3_result_int(pContext, 0);
    8735           0 :         return;
    8736             :     }
    8737           1 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8738             :     {
    8739           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8740           0 :         sqlite3_result_int(pContext, 0);
    8741           0 :         return;
    8742             :     }
    8743           1 :     const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
    8744           1 :     if (eGeomType == wkbUnknown)
    8745             :     {
    8746           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
    8747           0 :         sqlite3_result_int(pContext, 0);
    8748           0 :         return;
    8749             :     }
    8750             : 
    8751           1 :     sqlite3_result_int(
    8752             :         pContext,
    8753           1 :         static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
    8754             : }
    8755             : 
    8756             : /************************************************************************/
    8757             : /*                  OGRGeoPackageCreateSpatialIndex()                   */
    8758             : /************************************************************************/
    8759             : 
    8760          14 : static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
    8761             :                                             int /*argc*/, sqlite3_value **argv)
    8762             : {
    8763          27 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8764          13 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8765             :     {
    8766           2 :         sqlite3_result_int(pContext, 0);
    8767           2 :         return;
    8768             :     }
    8769             : 
    8770             :     const char *pszTableName =
    8771          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8772             :     const char *pszGeomName =
    8773          12 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8774             :     GDALGeoPackageDataset *poDS =
    8775          12 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8776             : 
    8777          12 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8778          12 :         poDS->GetLayerByName(pszTableName));
    8779          12 :     if (poLyr == nullptr)
    8780             :     {
    8781           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8782           1 :         sqlite3_result_int(pContext, 0);
    8783           1 :         return;
    8784             :     }
    8785          11 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8786             :     {
    8787           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8788           1 :         sqlite3_result_int(pContext, 0);
    8789           1 :         return;
    8790             :     }
    8791             : 
    8792          10 :     sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
    8793             : }
    8794             : 
    8795             : /************************************************************************/
    8796             : /*                  OGRGeoPackageDisableSpatialIndex()                  */
    8797             : /************************************************************************/
    8798             : 
    8799          12 : static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
    8800             :                                              int /*argc*/, sqlite3_value **argv)
    8801             : {
    8802          23 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8803          11 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8804             :     {
    8805           2 :         sqlite3_result_int(pContext, 0);
    8806           2 :         return;
    8807             :     }
    8808             : 
    8809             :     const char *pszTableName =
    8810          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8811             :     const char *pszGeomName =
    8812          10 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8813             :     GDALGeoPackageDataset *poDS =
    8814          10 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8815             : 
    8816          10 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8817          10 :         poDS->GetLayerByName(pszTableName));
    8818          10 :     if (poLyr == nullptr)
    8819             :     {
    8820           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8821           1 :         sqlite3_result_int(pContext, 0);
    8822           1 :         return;
    8823             :     }
    8824           9 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8825             :     {
    8826           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8827           1 :         sqlite3_result_int(pContext, 0);
    8828           1 :         return;
    8829             :     }
    8830             : 
    8831           8 :     sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
    8832             : }
    8833             : 
    8834             : /************************************************************************/
    8835             : /*                  OGRGeoPackageHasSpatialIndex()                      */
    8836             : /************************************************************************/
    8837             : 
    8838          29 : static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
    8839             :                                          int /*argc*/, sqlite3_value **argv)
    8840             : {
    8841          57 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8842          28 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8843             :     {
    8844           2 :         sqlite3_result_int(pContext, 0);
    8845           2 :         return;
    8846             :     }
    8847             : 
    8848             :     const char *pszTableName =
    8849          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8850             :     const char *pszGeomName =
    8851          27 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8852             :     GDALGeoPackageDataset *poDS =
    8853          27 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    8854             : 
    8855          27 :     OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
    8856          27 :         poDS->GetLayerByName(pszTableName));
    8857          27 :     if (poLyr == nullptr)
    8858             :     {
    8859           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
    8860           1 :         sqlite3_result_int(pContext, 0);
    8861           1 :         return;
    8862             :     }
    8863          26 :     if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
    8864             :     {
    8865           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
    8866           1 :         sqlite3_result_int(pContext, 0);
    8867           1 :         return;
    8868             :     }
    8869             : 
    8870          25 :     poLyr->RunDeferredCreationIfNecessary();
    8871          25 :     poLyr->CreateSpatialIndexIfNecessary();
    8872             : 
    8873          25 :     sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
    8874             : }
    8875             : 
    8876             : /************************************************************************/
    8877             : /*                       GPKG_hstore_get_value()                        */
    8878             : /************************************************************************/
    8879             : 
    8880           4 : static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
    8881             :                                   sqlite3_value **argv)
    8882             : {
    8883           7 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    8884           3 :         sqlite3_value_type(argv[1]) != SQLITE_TEXT)
    8885             :     {
    8886           2 :         sqlite3_result_null(pContext);
    8887           2 :         return;
    8888             :     }
    8889             : 
    8890             :     const char *pszHStore =
    8891           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    8892             :     const char *pszSearchedKey =
    8893           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
    8894           2 :     char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
    8895           2 :     if (pszValue != nullptr)
    8896           1 :         sqlite3_result_text(pContext, pszValue, -1, CPLFree);
    8897             :     else
    8898           1 :         sqlite3_result_null(pContext);
    8899             : }
    8900             : 
    8901             : /************************************************************************/
    8902             : /*                      GPKG_GDAL_GetMemFileFromBlob()                  */
    8903             : /************************************************************************/
    8904             : 
    8905         105 : static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
    8906             : {
    8907         105 :     int nBytes = sqlite3_value_bytes(argv[0]);
    8908             :     const GByte *pabyBLOB =
    8909         105 :         reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
    8910         105 :     CPLString osMemFileName;
    8911         105 :     osMemFileName.Printf("/vsimem/GPKG_GDAL_GetMemFileFromBlob_%p", argv);
    8912         105 :     VSILFILE *fp = VSIFileFromMemBuffer(
    8913             :         osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
    8914         105 :     VSIFCloseL(fp);
    8915         105 :     return osMemFileName;
    8916             : }
    8917             : 
    8918             : /************************************************************************/
    8919             : /*                       GPKG_GDAL_GetMimeType()                        */
    8920             : /************************************************************************/
    8921             : 
    8922          35 : static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
    8923             :                                   sqlite3_value **argv)
    8924             : {
    8925          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8926             :     {
    8927           0 :         sqlite3_result_null(pContext);
    8928           0 :         return;
    8929             :     }
    8930             : 
    8931          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    8932             :     GDALDriver *poDriver =
    8933          35 :         GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
    8934          35 :     if (poDriver != nullptr)
    8935             :     {
    8936          35 :         const char *pszRes = nullptr;
    8937          35 :         if (EQUAL(poDriver->GetDescription(), "PNG"))
    8938          23 :             pszRes = "image/png";
    8939          12 :         else if (EQUAL(poDriver->GetDescription(), "JPEG"))
    8940           6 :             pszRes = "image/jpeg";
    8941           6 :         else if (EQUAL(poDriver->GetDescription(), "WEBP"))
    8942           6 :             pszRes = "image/x-webp";
    8943           0 :         else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
    8944           0 :             pszRes = "image/tiff";
    8945             :         else
    8946           0 :             pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
    8947          35 :         sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
    8948             :     }
    8949             :     else
    8950           0 :         sqlite3_result_null(pContext);
    8951          35 :     VSIUnlink(osMemFileName);
    8952             : }
    8953             : 
    8954             : /************************************************************************/
    8955             : /*                       GPKG_GDAL_GetBandCount()                       */
    8956             : /************************************************************************/
    8957             : 
    8958          35 : static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
    8959             :                                    sqlite3_value **argv)
    8960             : {
    8961          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8962             :     {
    8963           0 :         sqlite3_result_null(pContext);
    8964           0 :         return;
    8965             :     }
    8966             : 
    8967          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    8968             :     auto poDS = std::unique_ptr<GDALDataset>(
    8969             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    8970          70 :                           nullptr, nullptr, nullptr));
    8971          35 :     if (poDS != nullptr)
    8972             :     {
    8973          35 :         sqlite3_result_int(pContext, poDS->GetRasterCount());
    8974             :     }
    8975             :     else
    8976           0 :         sqlite3_result_null(pContext);
    8977          35 :     VSIUnlink(osMemFileName);
    8978             : }
    8979             : 
    8980             : /************************************************************************/
    8981             : /*                       GPKG_GDAL_HasColorTable()                      */
    8982             : /************************************************************************/
    8983             : 
    8984          35 : static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
    8985             :                                     sqlite3_value **argv)
    8986             : {
    8987          35 :     if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
    8988             :     {
    8989           0 :         sqlite3_result_null(pContext);
    8990           0 :         return;
    8991             :     }
    8992             : 
    8993          70 :     CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
    8994             :     auto poDS = std::unique_ptr<GDALDataset>(
    8995             :         GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
    8996          70 :                           nullptr, nullptr, nullptr));
    8997          35 :     if (poDS != nullptr)
    8998             :     {
    8999          35 :         sqlite3_result_int(
    9000          46 :             pContext, poDS->GetRasterCount() == 1 &&
    9001          11 :                           poDS->GetRasterBand(1)->GetColorTable() != nullptr);
    9002             :     }
    9003             :     else
    9004           0 :         sqlite3_result_null(pContext);
    9005          35 :     VSIUnlink(osMemFileName);
    9006             : }
    9007             : 
    9008             : /************************************************************************/
    9009             : /*                      GetRasterLayerDataset()                         */
    9010             : /************************************************************************/
    9011             : 
    9012             : GDALDataset *
    9013           6 : GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
    9014             : {
    9015           6 :     auto oIter = m_oCachedRasterDS.find(pszLayerName);
    9016           6 :     if (oIter != m_oCachedRasterDS.end())
    9017           4 :         return oIter->second.get();
    9018             : 
    9019             :     auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
    9020           4 :         (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
    9021           4 :         GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    9022           2 :     if (!poDS)
    9023             :     {
    9024           0 :         return nullptr;
    9025             :     }
    9026           2 :     m_oCachedRasterDS[pszLayerName] = std::move(poDS);
    9027           2 :     return m_oCachedRasterDS[pszLayerName].get();
    9028             : }
    9029             : 
    9030             : /************************************************************************/
    9031             : /*                   GPKG_gdal_get_layer_pixel_value()                  */
    9032             : /************************************************************************/
    9033             : 
    9034          11 : static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext,
    9035             :                                             CPL_UNUSED int argc,
    9036             :                                             sqlite3_value **argv)
    9037             : {
    9038          11 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
    9039          10 :         sqlite3_value_type(argv[1]) != SQLITE_INTEGER ||
    9040           9 :         sqlite3_value_type(argv[2]) != SQLITE_TEXT ||
    9041           8 :         (sqlite3_value_type(argv[3]) != SQLITE_INTEGER &&
    9042          21 :          sqlite3_value_type(argv[3]) != SQLITE_FLOAT) ||
    9043           7 :         (sqlite3_value_type(argv[4]) != SQLITE_INTEGER &&
    9044           1 :          sqlite3_value_type(argv[4]) != SQLITE_FLOAT))
    9045             :     {
    9046           5 :         CPLError(CE_Failure, CPLE_AppDefined,
    9047             :                  "Invalid arguments to gdal_get_layer_pixel_value()");
    9048           5 :         sqlite3_result_null(pContext);
    9049           5 :         return;
    9050             :     }
    9051             : 
    9052             :     const char *pszLayerName =
    9053           6 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9054             : 
    9055             :     GDALGeoPackageDataset *poGlobalDS =
    9056           6 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9057           6 :     auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
    9058           6 :     if (!poDS)
    9059             :     {
    9060           0 :         sqlite3_result_null(pContext);
    9061           0 :         return;
    9062             :     }
    9063             : 
    9064           6 :     const int nBand = sqlite3_value_int(argv[1]);
    9065           6 :     auto poBand = poDS->GetRasterBand(nBand);
    9066           6 :     if (!poBand)
    9067             :     {
    9068           1 :         sqlite3_result_null(pContext);
    9069           1 :         return;
    9070             :     }
    9071             : 
    9072             :     const char *pszCoordType =
    9073           5 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
    9074             :     int x, y;
    9075           5 :     if (EQUAL(pszCoordType, "georef"))
    9076             :     {
    9077           1 :         const double X = sqlite3_value_double(argv[3]);
    9078           1 :         const double Y = sqlite3_value_double(argv[4]);
    9079             :         double adfGeoTransform[6];
    9080           1 :         if (poDS->GetGeoTransform(adfGeoTransform) != CE_None)
    9081             :         {
    9082           0 :             sqlite3_result_null(pContext);
    9083           0 :             return;
    9084             :         }
    9085             :         double adfInvGT[6];
    9086           1 :         if (!GDALInvGeoTransform(adfGeoTransform, adfInvGT))
    9087             :         {
    9088           0 :             sqlite3_result_null(pContext);
    9089           0 :             return;
    9090             :         }
    9091           1 :         x = static_cast<int>(adfInvGT[0] + X * adfInvGT[1] + Y * adfInvGT[2]);
    9092           1 :         y = static_cast<int>(adfInvGT[3] + X * adfInvGT[4] + Y * adfInvGT[5]);
    9093             :     }
    9094           4 :     else if (EQUAL(pszCoordType, "pixel"))
    9095             :     {
    9096           3 :         x = sqlite3_value_int(argv[3]);
    9097           3 :         y = sqlite3_value_int(argv[4]);
    9098             :     }
    9099             :     else
    9100             :     {
    9101           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    9102             :                  "Invalid value for 3rd argument of gdal_get_pixel_value(): "
    9103             :                  "only 'georef' or 'pixel' are supported");
    9104           1 :         sqlite3_result_null(pContext);
    9105           1 :         return;
    9106             :     }
    9107           7 :     if (x < 0 || x >= poDS->GetRasterXSize() || y < 0 ||
    9108           3 :         y >= poDS->GetRasterYSize())
    9109             :     {
    9110           1 :         sqlite3_result_null(pContext);
    9111           1 :         return;
    9112             :     }
    9113           3 :     const auto eDT = poBand->GetRasterDataType();
    9114           3 :     if (eDT != GDT_UInt64 && GDALDataTypeIsInteger(eDT))
    9115             :     {
    9116           2 :         int64_t nValue = 0;
    9117           2 :         if (poBand->RasterIO(GF_Read, x, y, 1, 1, &nValue, 1, 1, GDT_Int64, 0,
    9118           2 :                              0, nullptr) != CE_None)
    9119             :         {
    9120           0 :             sqlite3_result_null(pContext);
    9121           0 :             return;
    9122             :         }
    9123           2 :         return sqlite3_result_int64(pContext, nValue);
    9124             :     }
    9125             :     else
    9126             :     {
    9127           1 :         double dfValue = 0;
    9128           1 :         if (poBand->RasterIO(GF_Read, x, y, 1, 1, &dfValue, 1, 1, GDT_Float64,
    9129           1 :                              0, 0, nullptr) != CE_None)
    9130             :         {
    9131           0 :             sqlite3_result_null(pContext);
    9132           0 :             return;
    9133             :         }
    9134           1 :         return sqlite3_result_double(pContext, dfValue);
    9135             :     }
    9136             : }
    9137             : 
    9138             : /************************************************************************/
    9139             : /*                       GPKG_ogr_layer_Extent()                        */
    9140             : /************************************************************************/
    9141             : 
    9142           3 : static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
    9143             :                                   sqlite3_value **argv)
    9144             : {
    9145           3 :     if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
    9146             :     {
    9147           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
    9148             :                  "ogr_layer_Extent");
    9149           1 :         sqlite3_result_null(pContext);
    9150           2 :         return;
    9151             :     }
    9152             : 
    9153             :     const char *pszLayerName =
    9154           2 :         reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
    9155             :     GDALGeoPackageDataset *poDS =
    9156           2 :         static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
    9157           2 :     OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
    9158           2 :     if (!poLayer)
    9159             :     {
    9160           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
    9161             :                  "ogr_layer_Extent");
    9162           1 :         sqlite3_result_null(pContext);
    9163           1 :         return;
    9164             :     }
    9165             : 
    9166           1 :     if (poLayer->GetGeomType() == wkbNone)
    9167             :     {
    9168           0 :         sqlite3_result_null(pContext);
    9169           0 :         return;
    9170             :     }
    9171             : 
    9172           1 :     OGREnvelope sExtent;
    9173           1 :     if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
    9174             :     {
    9175           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
    9176             :                  "ogr_layer_Extent");
    9177           0 :         sqlite3_result_null(pContext);
    9178           0 :         return;
    9179             :     }
    9180             : 
    9181           1 :     OGRPolygon oPoly;
    9182           1 :     OGRLinearRing *poRing = new OGRLinearRing();
    9183           1 :     oPoly.addRingDirectly(poRing);
    9184           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9185           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MinY);
    9186           1 :     poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
    9187           1 :     poRing->addPoint(sExtent.MinX, sExtent.MaxY);
    9188           1 :     poRing->addPoint(sExtent.MinX, sExtent.MinY);
    9189             : 
    9190           1 :     const auto poSRS = poLayer->GetSpatialRef();
    9191           1 :     const int nSRID = poDS->GetSrsId(poSRS);
    9192           1 :     size_t nBLOBDestLen = 0;
    9193             :     GByte *pabyDestBLOB =
    9194           1 :         GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
    9195           1 :     if (!pabyDestBLOB)
    9196             :     {
    9197           0 :         sqlite3_result_null(pContext);
    9198           0 :         return;
    9199             :     }
    9200           1 :     sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
    9201             :                         VSIFree);
    9202             : }
    9203             : 
    9204             : /************************************************************************/
    9205             : /*                      InstallSQLFunctions()                           */
    9206             : /************************************************************************/
    9207             : 
    9208             : #ifndef SQLITE_DETERMINISTIC
    9209             : #define SQLITE_DETERMINISTIC 0
    9210             : #endif
    9211             : 
    9212             : #ifndef SQLITE_INNOCUOUS
    9213             : #define SQLITE_INNOCUOUS 0
    9214             : #endif
    9215             : 
    9216             : #ifndef UTF8_INNOCUOUS
    9217             : #define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
    9218             : #endif
    9219             : 
    9220        1671 : void GDALGeoPackageDataset::InstallSQLFunctions()
    9221             : {
    9222        1671 :     InitSpatialite();
    9223             : 
    9224             :     // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
    9225             :     // that take geometries will accept GPKG encoded geometries without
    9226             :     // explicit conversion.
    9227             :     // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
    9228             :     // error.
    9229        1671 :     sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
    9230             :                  nullptr);
    9231             : 
    9232             :     /* Used by RTree Spatial Index Extension */
    9233        1671 :     sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
    9234             :                             OGRGeoPackageSTMinX, nullptr, nullptr);
    9235        1671 :     sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
    9236             :                             OGRGeoPackageSTMinY, nullptr, nullptr);
    9237        1671 :     sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
    9238             :                             OGRGeoPackageSTMaxX, nullptr, nullptr);
    9239        1671 :     sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
    9240             :                             OGRGeoPackageSTMaxY, nullptr, nullptr);
    9241        1671 :     sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
    9242             :                             OGRGeoPackageSTIsEmpty, nullptr, nullptr);
    9243             : 
    9244             :     /* Used by Geometry Type Triggers Extension */
    9245        1671 :     sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
    9246             :                             OGRGeoPackageSTGeometryType, nullptr, nullptr);
    9247        1671 :     sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
    9248             :                             nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
    9249             :                             nullptr);
    9250             : 
    9251             :     /* Used by Geometry SRS ID Triggers Extension */
    9252        1671 :     sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
    9253             :                             OGRGeoPackageSTSRID, nullptr, nullptr);
    9254             : 
    9255             :     /* Spatialite-like functions */
    9256        1671 :     sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
    9257             :                             OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
    9258        1671 :     sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
    9259             :                             OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
    9260        1671 :     sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
    9261             :                             OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
    9262             : 
    9263             :     // HSTORE functions
    9264        1671 :     sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
    9265             :                             GPKG_hstore_get_value, nullptr, nullptr);
    9266             : 
    9267             :     // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
    9268        1671 :     sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
    9269             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9270        1671 :     sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
    9271             :                             OGRGeoPackageTransform, nullptr, nullptr);
    9272        1671 :     sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
    9273             :                             OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
    9274             : 
    9275        1671 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9276             :                             OGRGeoPackageSTEnvelopesIntersectsTwoParams,
    9277             :                             nullptr, nullptr);
    9278        1671 :     sqlite3_create_function(
    9279             :         hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
    9280             :         OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
    9281             : 
    9282        1671 :     sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
    9283             :                             OGRGeoPackageSTEnvelopesIntersects, nullptr,
    9284             :                             nullptr);
    9285        1671 :     sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
    9286             :                             nullptr, OGRGeoPackageSTEnvelopesIntersects,
    9287             :                             nullptr, nullptr);
    9288             : 
    9289             :     // Implementation that directly hacks the GeoPackage geometry blob header
    9290        1671 :     sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
    9291             :                             OGRGeoPackageSetSRID, nullptr, nullptr);
    9292             : 
    9293             :     // GDAL specific function
    9294        1671 :     sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
    9295             :                             OGRGeoPackageImportFromEPSG, nullptr, nullptr);
    9296             : 
    9297             :     // May be used by ogrmerge.py
    9298        1671 :     sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
    9299             :                             this, OGRGeoPackageRegisterGeometryExtension,
    9300             :                             nullptr, nullptr);
    9301             : 
    9302        1671 :     if (OGRGeometryFactory::haveGEOS())
    9303             :     {
    9304        1671 :         sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
    9305             :                                 OGRGeoPackageSTMakeValid, nullptr, nullptr);
    9306             :     }
    9307             : 
    9308        1671 :     sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
    9309             :                             OGRGeoPackageSTArea, nullptr, nullptr);
    9310        1671 :     sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
    9311             :                             OGRGeoPackageGeodesicArea, nullptr, nullptr);
    9312             : 
    9313             :     // Debug functions
    9314        1671 :     if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
    9315             :     {
    9316         409 :         sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
    9317             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9318             :                                 GPKG_GDAL_GetMimeType, nullptr, nullptr);
    9319         409 :         sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
    9320             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9321             :                                 GPKG_GDAL_GetBandCount, nullptr, nullptr);
    9322         409 :         sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
    9323             :                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
    9324             :                                 GPKG_GDAL_HasColorTable, nullptr, nullptr);
    9325             :     }
    9326             : 
    9327        1671 :     sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
    9328             :                             this, GPKG_gdal_get_layer_pixel_value, nullptr,
    9329             :                             nullptr);
    9330             : 
    9331             :     // Function from VirtualOGR
    9332        1671 :     sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
    9333             :                             GPKG_ogr_layer_Extent, nullptr, nullptr);
    9334             : 
    9335        1671 :     m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
    9336        1671 : }
    9337             : 
    9338             : /************************************************************************/
    9339             : /*                         OpenOrCreateDB()                             */
    9340             : /************************************************************************/
    9341             : 
    9342        1672 : bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
    9343             : {
    9344        1672 :     const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
    9345             :         flags, /*bRegisterOGR2SQLiteExtensions=*/false,
    9346             :         /*bLoadExtensions=*/true);
    9347        1672 :     if (!bSuccess)
    9348           6 :         return false;
    9349             : 
    9350             :     // Turning on recursive_triggers is needed so that DELETE triggers fire
    9351             :     // in a INSERT OR REPLACE statement. In particular this is needed to
    9352             :     // make sure gpkg_ogr_contents.feature_count is properly updated.
    9353        1666 :     SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
    9354             : 
    9355        1666 :     InstallSQLFunctions();
    9356             : 
    9357             :     const char *pszSqlitePragma =
    9358        1666 :         CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
    9359        1666 :     OGRErr eErr = OGRERR_NONE;
    9360           5 :     if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
    9361             :         // Older sqlite versions don't have this pragma
    9362        3337 :         SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
    9363        1666 :         eErr == OGRERR_NONE)
    9364             :     {
    9365        1666 :         bool bNeedsTrustedSchema = false;
    9366             : 
    9367             :         // Current SQLite versions require PRAGMA trusted_schema = 1 to be
    9368             :         // able to use the RTree from triggers, which is only needed when
    9369             :         // modifying the RTree.
    9370        4093 :         if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
    9371        2571 :              (flags & SQLITE_OPEN_CREATE) != 0) &&
    9372         905 :             OGRSQLiteRTreeRequiresTrustedSchemaOn())
    9373             :         {
    9374         905 :             bNeedsTrustedSchema = true;
    9375             :         }
    9376             : 
    9377             : #ifdef HAVE_SPATIALITE
    9378             :         // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
    9379         761 :         if (!bNeedsTrustedSchema && HasExtensionsTable() &&
    9380         683 :             SQLGetInteger(
    9381             :                 hDB,
    9382             :                 "SELECT 1 FROM gpkg_extensions WHERE "
    9383             :                 "extension_name ='gdal_spatialite_computed_geom_column'",
    9384           1 :                 nullptr) == 1 &&
    9385        2427 :             SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
    9386             :         {
    9387           1 :             bNeedsTrustedSchema = true;
    9388             :         }
    9389             : #endif
    9390             : 
    9391        1666 :         if (bNeedsTrustedSchema)
    9392             :         {
    9393         906 :             CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
    9394         906 :             SQLCommand(hDB, "PRAGMA trusted_schema = 1");
    9395             :         }
    9396             :     }
    9397             : 
    9398        1666 :     return true;
    9399             : }
    9400             : 
    9401             : /************************************************************************/
    9402             : /*                   GetLayerWithGetSpatialWhereByName()                */
    9403             : /************************************************************************/
    9404             : 
    9405             : std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
    9406          90 : GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
    9407             : {
    9408             :     OGRGeoPackageLayer *poRet =
    9409          90 :         cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
    9410          90 :     return std::pair(poRet, poRet);
    9411             : }
    9412             : 
    9413             : /************************************************************************/
    9414             : /*                       CommitTransaction()                            */
    9415             : /************************************************************************/
    9416             : 
    9417         143 : OGRErr GDALGeoPackageDataset::CommitTransaction()
    9418             : 
    9419             : {
    9420         143 :     if (nSoftTransactionLevel == 1)
    9421             :     {
    9422         142 :         FlushMetadata();
    9423         322 :         for (int i = 0; i < m_nLayers; i++)
    9424             :         {
    9425         180 :             m_papoLayers[i]->DoJobAtTransactionCommit();
    9426             :         }
    9427             :     }
    9428             : 
    9429         143 :     return OGRSQLiteBaseDataSource::CommitTransaction();
    9430             : }
    9431             : 
    9432             : /************************************************************************/
    9433             : /*                     RollbackTransaction()                            */
    9434             : /************************************************************************/
    9435             : 
    9436          29 : OGRErr GDALGeoPackageDataset::RollbackTransaction()
    9437             : 
    9438             : {
    9439             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9440          58 :     std::vector<bool> abAddTriggers;
    9441          29 :     std::vector<bool> abTriggersDeletedInTransaction;
    9442             : #endif
    9443          29 :     if (nSoftTransactionLevel == 1)
    9444             :     {
    9445          28 :         FlushMetadata();
    9446          58 :         for (int i = 0; i < m_nLayers; i++)
    9447             :         {
    9448             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9449          30 :             abAddTriggers.push_back(
    9450          30 :                 m_papoLayers[i]->GetAddOGRFeatureCountTriggers());
    9451          30 :             abTriggersDeletedInTransaction.push_back(
    9452          30 :                 m_papoLayers[i]
    9453          30 :                     ->GetOGRFeatureCountTriggersDeletedInTransaction());
    9454          30 :             m_papoLayers[i]->SetAddOGRFeatureCountTriggers(false);
    9455             : #endif
    9456          30 :             m_papoLayers[i]->DoJobAtTransactionRollback();
    9457             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9458          30 :             m_papoLayers[i]->DisableFeatureCount();
    9459             : #endif
    9460             :         }
    9461             :     }
    9462             : 
    9463          29 :     OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
    9464             : #ifdef ENABLE_GPKG_OGR_CONTENTS
    9465          29 :     if (!abAddTriggers.empty())
    9466             :     {
    9467          56 :         for (int i = 0; i < m_nLayers; i++)
    9468             :         {
    9469          30 :             if (abTriggersDeletedInTransaction[i])
    9470             :             {
    9471           7 :                 m_papoLayers[i]->SetOGRFeatureCountTriggersEnabled(true);
    9472             :             }
    9473             :             else
    9474             :             {
    9475          23 :                 m_papoLayers[i]->SetAddOGRFeatureCountTriggers(
    9476          46 :                     abAddTriggers[i]);
    9477             :             }
    9478             :         }
    9479             :     }
    9480             : #endif
    9481          58 :     return eErr;
    9482             : }
    9483             : 
    9484             : /************************************************************************/
    9485             : /*                       GetGeometryTypeString()                        */
    9486             : /************************************************************************/
    9487             : 
    9488             : const char *
    9489        1177 : GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
    9490             : {
    9491        1177 :     const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
    9492        1189 :     if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
    9493          12 :         CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
    9494             :     {
    9495           0 :         pszGPKGGeomType = "GEOMCOLLECTION";
    9496             :     }
    9497        1177 :     return pszGPKGGeomType;
    9498             : }
    9499             : 
    9500             : /************************************************************************/
    9501             : /*                           GetFieldDomainNames()                      */
    9502             : /************************************************************************/
    9503             : 
    9504             : std::vector<std::string>
    9505          10 : GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
    9506             : {
    9507          10 :     if (!HasDataColumnConstraintsTable())
    9508           3 :         return std::vector<std::string>();
    9509             : 
    9510          14 :     std::vector<std::string> oDomainNamesList;
    9511             : 
    9512           7 :     std::unique_ptr<SQLResult> oResultTable;
    9513             :     {
    9514             :         std::string osSQL =
    9515             :             "SELECT DISTINCT constraint_name "
    9516             :             "FROM gpkg_data_column_constraints "
    9517             :             "WHERE constraint_name NOT LIKE '_%_domain_description' "
    9518             :             "ORDER BY constraint_name "
    9519           7 :             "LIMIT 10000"  // to avoid denial of service
    9520             :             ;
    9521           7 :         oResultTable = SQLQuery(hDB, osSQL.c_str());
    9522           7 :         if (!oResultTable)
    9523           0 :             return oDomainNamesList;
    9524             :     }
    9525             : 
    9526           7 :     if (oResultTable->RowCount() == 10000)
    9527             :     {
    9528           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9529             :                  "Number of rows returned for field domain names has been "
    9530             :                  "truncated.");
    9531             :     }
    9532           7 :     else if (oResultTable->RowCount() > 0)
    9533             :     {
    9534           7 :         oDomainNamesList.reserve(oResultTable->RowCount());
    9535          89 :         for (int i = 0; i < oResultTable->RowCount(); i++)
    9536             :         {
    9537          82 :             const char *pszConstraintName = oResultTable->GetValue(0, i);
    9538          82 :             if (!pszConstraintName)
    9539           0 :                 continue;
    9540             : 
    9541          82 :             oDomainNamesList.emplace_back(pszConstraintName);
    9542             :         }
    9543             :     }
    9544             : 
    9545           7 :     return oDomainNamesList;
    9546             : }
    9547             : 
    9548             : /************************************************************************/
    9549             : /*                           GetFieldDomain()                           */
    9550             : /************************************************************************/
    9551             : 
    9552             : const OGRFieldDomain *
    9553         102 : GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
    9554             : {
    9555         102 :     const auto baseRet = GDALDataset::GetFieldDomain(name);
    9556         102 :     if (baseRet)
    9557          42 :         return baseRet;
    9558             : 
    9559          60 :     if (!HasDataColumnConstraintsTable())
    9560           4 :         return nullptr;
    9561             : 
    9562          56 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9563          56 :     const char *min_is_inclusive =
    9564          56 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9565          56 :     const char *max_is_inclusive =
    9566          56 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9567             : 
    9568          56 :     std::unique_ptr<SQLResult> oResultTable;
    9569             :     // Note: for coded domains, we use a little trick by using a dummy
    9570             :     // _{domainname}_domain_description enum that has a single entry whose
    9571             :     // description is the description of the main domain.
    9572             :     {
    9573          56 :         char *pszSQL = sqlite3_mprintf(
    9574             :             "SELECT constraint_type, value, min, %s, "
    9575             :             "max, %s, description, constraint_name "
    9576             :             "FROM gpkg_data_column_constraints "
    9577             :             "WHERE constraint_name IN ('%q', "
    9578             :             "'_%q_domain_description') "
    9579             :             "AND length(constraint_type) < 100 "  // to
    9580             :                                                   // avoid
    9581             :                                                   // denial
    9582             :                                                   // of
    9583             :                                                   // service
    9584             :             "AND (value IS NULL OR length(value) < "
    9585             :             "10000) "  // to avoid denial
    9586             :                        // of service
    9587             :             "AND (description IS NULL OR "
    9588             :             "length(description) < 10000) "  // to
    9589             :                                              // avoid
    9590             :                                              // denial
    9591             :                                              // of
    9592             :                                              // service
    9593             :             "ORDER BY value "
    9594             :             "LIMIT 10000",  // to avoid denial of
    9595             :                             // service
    9596             :             min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
    9597          56 :         oResultTable = SQLQuery(hDB, pszSQL);
    9598          56 :         sqlite3_free(pszSQL);
    9599          56 :         if (!oResultTable)
    9600           0 :             return nullptr;
    9601             :     }
    9602          56 :     if (oResultTable->RowCount() == 0)
    9603             :     {
    9604          15 :         return nullptr;
    9605             :     }
    9606          41 :     if (oResultTable->RowCount() == 10000)
    9607             :     {
    9608           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    9609             :                  "Number of rows returned for field domain %s has been "
    9610             :                  "truncated.",
    9611             :                  name.c_str());
    9612             :     }
    9613             : 
    9614             :     // Try to find the field domain data type from fields that implement it
    9615          41 :     int nFieldType = -1;
    9616          41 :     OGRFieldSubType eSubType = OFSTNone;
    9617          41 :     if (HasDataColumnsTable())
    9618             :     {
    9619          36 :         char *pszSQL = sqlite3_mprintf(
    9620             :             "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
    9621             :             "constraint_name = '%q' LIMIT 10",
    9622             :             name.c_str());
    9623          72 :         auto oResultTable2 = SQLQuery(hDB, pszSQL);
    9624          36 :         sqlite3_free(pszSQL);
    9625          36 :         if (oResultTable2 && oResultTable2->RowCount() >= 1)
    9626             :         {
    9627          46 :             for (int iRecord = 0; iRecord < oResultTable2->RowCount();
    9628             :                  iRecord++)
    9629             :             {
    9630          23 :                 const char *pszTableName = oResultTable2->GetValue(0, iRecord);
    9631          23 :                 const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
    9632          23 :                 if (pszTableName == nullptr || pszColumnName == nullptr)
    9633           0 :                     continue;
    9634             :                 OGRLayer *poLayer =
    9635          46 :                     const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
    9636          23 :                         pszTableName);
    9637          23 :                 if (poLayer)
    9638             :                 {
    9639          23 :                     const auto poFDefn = poLayer->GetLayerDefn();
    9640          23 :                     int nIdx = poFDefn->GetFieldIndex(pszColumnName);
    9641          23 :                     if (nIdx >= 0)
    9642             :                     {
    9643          23 :                         const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
    9644          23 :                         const auto eType = poFieldDefn->GetType();
    9645          23 :                         if (nFieldType < 0)
    9646             :                         {
    9647          23 :                             nFieldType = eType;
    9648          23 :                             eSubType = poFieldDefn->GetSubType();
    9649             :                         }
    9650           0 :                         else if ((eType == OFTInteger64 || eType == OFTReal) &&
    9651             :                                  nFieldType == OFTInteger)
    9652             :                         {
    9653             :                             // ok
    9654             :                         }
    9655           0 :                         else if (eType == OFTInteger &&
    9656           0 :                                  (nFieldType == OFTInteger64 ||
    9657             :                                   nFieldType == OFTReal))
    9658             :                         {
    9659           0 :                             nFieldType = OFTInteger;
    9660           0 :                             eSubType = OFSTNone;
    9661             :                         }
    9662           0 :                         else if (nFieldType != eType)
    9663             :                         {
    9664           0 :                             nFieldType = -1;
    9665           0 :                             eSubType = OFSTNone;
    9666           0 :                             break;
    9667             :                         }
    9668             :                     }
    9669             :                 }
    9670             :             }
    9671             :         }
    9672             :     }
    9673             : 
    9674          41 :     std::unique_ptr<OGRFieldDomain> poDomain;
    9675          82 :     std::vector<OGRCodedValue> asValues;
    9676          41 :     bool error = false;
    9677          82 :     CPLString osLastConstraintType;
    9678          41 :     int nFieldTypeFromEnumCode = -1;
    9679          82 :     std::string osConstraintDescription;
    9680          82 :     std::string osDescrConstraintName("_");
    9681          41 :     osDescrConstraintName += name;
    9682          41 :     osDescrConstraintName += "_domain_description";
    9683         100 :     for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
    9684             :     {
    9685          63 :         const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
    9686          63 :         if (pszConstraintType == nullptr)
    9687           0 :             continue;
    9688          63 :         const char *pszValue = oResultTable->GetValue(1, iRecord);
    9689          63 :         const char *pszMin = oResultTable->GetValue(2, iRecord);
    9690             :         const bool bIsMinIncluded =
    9691          63 :             oResultTable->GetValueAsInteger(3, iRecord) == 1;
    9692          63 :         const char *pszMax = oResultTable->GetValue(4, iRecord);
    9693             :         const bool bIsMaxIncluded =
    9694          63 :             oResultTable->GetValueAsInteger(5, iRecord) == 1;
    9695          63 :         const char *pszDescription = oResultTable->GetValue(6, iRecord);
    9696          63 :         const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
    9697             : 
    9698          63 :         if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
    9699             :         {
    9700           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9701             :                      "Only constraint of type 'enum' can have multiple rows");
    9702           1 :             error = true;
    9703           1 :             break;
    9704             :         }
    9705             : 
    9706          62 :         if (strcmp(pszConstraintType, "enum") == 0)
    9707             :         {
    9708          42 :             if (pszValue == nullptr)
    9709             :             {
    9710           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9711             :                          "NULL in 'value' column of enumeration");
    9712           1 :                 error = true;
    9713           1 :                 break;
    9714             :             }
    9715          41 :             if (osDescrConstraintName == pszConstraintName)
    9716             :             {
    9717           1 :                 if (pszDescription)
    9718             :                 {
    9719           1 :                     osConstraintDescription = pszDescription;
    9720             :                 }
    9721           1 :                 continue;
    9722             :             }
    9723          40 :             if (asValues.empty())
    9724             :             {
    9725          20 :                 asValues.reserve(oResultTable->RowCount() + 1);
    9726             :             }
    9727             :             OGRCodedValue cv;
    9728             :             // intended: the 'value' column in GPKG is actually the code
    9729          40 :             cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
    9730          40 :             if (cv.pszCode == nullptr)
    9731             :             {
    9732           0 :                 error = true;
    9733           0 :                 break;
    9734             :             }
    9735          40 :             if (pszDescription)
    9736             :             {
    9737          29 :                 cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
    9738          29 :                 if (cv.pszValue == nullptr)
    9739             :                 {
    9740           0 :                     VSIFree(cv.pszCode);
    9741           0 :                     error = true;
    9742           0 :                     break;
    9743             :                 }
    9744             :             }
    9745             :             else
    9746             :             {
    9747          11 :                 cv.pszValue = nullptr;
    9748             :             }
    9749             : 
    9750             :             // If we can't get the data type from field definition, guess it
    9751             :             // from code.
    9752          40 :             if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
    9753             :             {
    9754          18 :                 switch (CPLGetValueType(cv.pszCode))
    9755             :                 {
    9756          13 :                     case CPL_VALUE_INTEGER:
    9757             :                     {
    9758          13 :                         if (nFieldTypeFromEnumCode != OFTReal &&
    9759             :                             nFieldTypeFromEnumCode != OFTInteger64)
    9760             :                         {
    9761           9 :                             const auto nVal = CPLAtoGIntBig(cv.pszCode);
    9762          17 :                             if (nVal < std::numeric_limits<int>::min() ||
    9763           8 :                                 nVal > std::numeric_limits<int>::max())
    9764             :                             {
    9765           3 :                                 nFieldTypeFromEnumCode = OFTInteger64;
    9766             :                             }
    9767             :                             else
    9768             :                             {
    9769           6 :                                 nFieldTypeFromEnumCode = OFTInteger;
    9770             :                             }
    9771             :                         }
    9772          13 :                         break;
    9773             :                     }
    9774             : 
    9775           3 :                     case CPL_VALUE_REAL:
    9776           3 :                         nFieldTypeFromEnumCode = OFTReal;
    9777           3 :                         break;
    9778             : 
    9779           2 :                     case CPL_VALUE_STRING:
    9780           2 :                         nFieldTypeFromEnumCode = OFTString;
    9781           2 :                         break;
    9782             :                 }
    9783             :             }
    9784             : 
    9785          40 :             asValues.emplace_back(cv);
    9786             :         }
    9787          20 :         else if (strcmp(pszConstraintType, "range") == 0)
    9788             :         {
    9789             :             OGRField sMin;
    9790             :             OGRField sMax;
    9791          14 :             OGR_RawField_SetUnset(&sMin);
    9792          14 :             OGR_RawField_SetUnset(&sMax);
    9793          14 :             if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
    9794           8 :                 nFieldType = OFTReal;
    9795          27 :             if (pszMin != nullptr &&
    9796          13 :                 CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
    9797             :             {
    9798          10 :                 if (nFieldType == OFTInteger)
    9799           3 :                     sMin.Integer = atoi(pszMin);
    9800           7 :                 else if (nFieldType == OFTInteger64)
    9801           3 :                     sMin.Integer64 = CPLAtoGIntBig(pszMin);
    9802             :                 else /* if( nFieldType == OFTReal ) */
    9803           4 :                     sMin.Real = CPLAtof(pszMin);
    9804             :             }
    9805          27 :             if (pszMax != nullptr &&
    9806          13 :                 CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
    9807             :             {
    9808          10 :                 if (nFieldType == OFTInteger)
    9809           3 :                     sMax.Integer = atoi(pszMax);
    9810           7 :                 else if (nFieldType == OFTInteger64)
    9811           3 :                     sMax.Integer64 = CPLAtoGIntBig(pszMax);
    9812             :                 else /* if( nFieldType == OFTReal ) */
    9813           4 :                     sMax.Real = CPLAtof(pszMax);
    9814             :             }
    9815          28 :             poDomain.reset(new OGRRangeFieldDomain(
    9816             :                 name, pszDescription ? pszDescription : "",
    9817             :                 static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
    9818          14 :                 bIsMinIncluded, sMax, bIsMaxIncluded));
    9819             :         }
    9820           6 :         else if (strcmp(pszConstraintType, "glob") == 0)
    9821             :         {
    9822           5 :             if (pszValue == nullptr)
    9823             :             {
    9824           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9825             :                          "NULL in 'value' column of glob");
    9826           1 :                 error = true;
    9827           1 :                 break;
    9828             :             }
    9829           4 :             if (nFieldType < 0)
    9830           1 :                 nFieldType = OFTString;
    9831           8 :             poDomain.reset(new OGRGlobFieldDomain(
    9832             :                 name, pszDescription ? pszDescription : "",
    9833           4 :                 static_cast<OGRFieldType>(nFieldType), eSubType, pszValue));
    9834             :         }
    9835             :         else
    9836             :         {
    9837           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    9838             :                      "Unhandled constraint_type: %s", pszConstraintType);
    9839           1 :             error = true;
    9840           1 :             break;
    9841             :         }
    9842             : 
    9843          58 :         osLastConstraintType = pszConstraintType;
    9844             :     }
    9845             : 
    9846          41 :     if (!asValues.empty())
    9847             :     {
    9848          20 :         if (nFieldType < 0)
    9849           9 :             nFieldType = nFieldTypeFromEnumCode;
    9850          20 :         poDomain.reset(
    9851             :             new OGRCodedFieldDomain(name, osConstraintDescription,
    9852             :                                     static_cast<OGRFieldType>(nFieldType),
    9853          20 :                                     eSubType, std::move(asValues)));
    9854             :     }
    9855             : 
    9856          41 :     if (error)
    9857             :     {
    9858           4 :         return nullptr;
    9859             :     }
    9860             : 
    9861          37 :     m_oMapFieldDomains[name] = std::move(poDomain);
    9862          37 :     return GDALDataset::GetFieldDomain(name);
    9863             : }
    9864             : 
    9865             : /************************************************************************/
    9866             : /*                           AddFieldDomain()                           */
    9867             : /************************************************************************/
    9868             : 
    9869          18 : bool GDALGeoPackageDataset::AddFieldDomain(
    9870             :     std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
    9871             : {
    9872          36 :     const std::string domainName(domain->GetName());
    9873          18 :     if (!GetUpdate())
    9874             :     {
    9875           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    9876             :                  "AddFieldDomain() not supported on read-only dataset");
    9877           0 :         return false;
    9878             :     }
    9879          18 :     if (GetFieldDomain(domainName) != nullptr)
    9880             :     {
    9881           1 :         failureReason = "A domain of identical name already exists";
    9882           1 :         return false;
    9883             :     }
    9884          17 :     if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
    9885           0 :         return false;
    9886             : 
    9887          17 :     const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
    9888          17 :     const char *min_is_inclusive =
    9889          17 :         bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
    9890          17 :     const char *max_is_inclusive =
    9891          17 :         bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
    9892             : 
    9893          17 :     const auto &osDescription = domain->GetDescription();
    9894          17 :     switch (domain->GetDomainType())
    9895             :     {
    9896          11 :         case OFDT_CODED:
    9897             :         {
    9898             :             const auto poCodedDomain =
    9899          11 :                 cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
    9900          11 :             if (!osDescription.empty())
    9901             :             {
    9902             :                 // We use a little trick by using a dummy
    9903             :                 // _{domainname}_domain_description enum that has a single
    9904             :                 // entry whose description is the description of the main
    9905             :                 // domain.
    9906           1 :                 char *pszSQL = sqlite3_mprintf(
    9907             :                     "INSERT INTO gpkg_data_column_constraints ("
    9908             :                     "constraint_name, constraint_type, value, "
    9909             :                     "min, %s, max, %s, "
    9910             :                     "description) VALUES ("
    9911             :                     "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
    9912             :                     "NULL, %Q)",
    9913             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
    9914             :                     osDescription.c_str());
    9915           1 :                 CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
    9916           1 :                 sqlite3_free(pszSQL);
    9917             :             }
    9918          11 :             const auto &enumeration = poCodedDomain->GetEnumeration();
    9919          33 :             for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
    9920             :             {
    9921          22 :                 char *pszSQL = sqlite3_mprintf(
    9922             :                     "INSERT INTO gpkg_data_column_constraints ("
    9923             :                     "constraint_name, constraint_type, value, "
    9924             :                     "min, %s, max, %s, "
    9925             :                     "description) VALUES ("
    9926             :                     "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
    9927             :                     min_is_inclusive, max_is_inclusive, domainName.c_str(),
    9928          22 :                     enumeration[i].pszCode, enumeration[i].pszValue);
    9929          22 :                 bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
    9930          22 :                 sqlite3_free(pszSQL);
    9931          22 :                 if (!ok)
    9932           0 :                     return false;
    9933             :             }
    9934          11 :             break;
    9935             :         }
    9936             : 
    9937           5 :         case OFDT_RANGE:
    9938             :         {
    9939             :             const auto poRangeDomain =
    9940           5 :                 cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
    9941           5 :             const auto eFieldType = poRangeDomain->GetFieldType();
    9942           5 :             if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
    9943             :                 eFieldType != OFTReal)
    9944             :             {
    9945             :                 failureReason = "Only range domains of numeric type are "
    9946           0 :                                 "supported in GeoPackage";
    9947           0 :                 return false;
    9948             :             }
    9949             : 
    9950           5 :             double dfMin = -std::numeric_limits<double>::infinity();
    9951           5 :             double dfMax = std::numeric_limits<double>::infinity();
    9952           5 :             bool bMinIsInclusive = true;
    9953           5 :             const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
    9954           5 :             bool bMaxIsInclusive = true;
    9955           5 :             const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
    9956           5 :             if (eFieldType == OFTInteger)
    9957             :             {
    9958           1 :                 if (!OGR_RawField_IsUnset(&sMin))
    9959           1 :                     dfMin = sMin.Integer;
    9960           1 :                 if (!OGR_RawField_IsUnset(&sMax))
    9961           1 :                     dfMax = sMax.Integer;
    9962             :             }
    9963           4 :             else if (eFieldType == OFTInteger64)
    9964             :             {
    9965           1 :                 if (!OGR_RawField_IsUnset(&sMin))
    9966           1 :                     dfMin = static_cast<double>(sMin.Integer64);
    9967           1 :                 if (!OGR_RawField_IsUnset(&sMax))
    9968           1 :                     dfMax = static_cast<double>(sMax.Integer64);
    9969             :             }
    9970             :             else /* if( eFieldType == OFTReal ) */
    9971             :             {
    9972           3 :                 if (!OGR_RawField_IsUnset(&sMin))
    9973           3 :                     dfMin = sMin.Real;
    9974           3 :                 if (!OGR_RawField_IsUnset(&sMax))
    9975           3 :                     dfMax = sMax.Real;
    9976             :             }
    9977             : 
    9978           5 :             sqlite3_stmt *hInsertStmt = nullptr;
    9979             :             const char *pszSQL =
    9980           5 :                 CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
    9981             :                            "constraint_name, constraint_type, value, "
    9982             :                            "min, %s, max, %s, "
    9983             :                            "description) VALUES ("
    9984             :                            "?, 'range', NULL, ?, ?, ?, ?, ?)",
    9985             :                            min_is_inclusive, max_is_inclusive);
    9986           5 :             if (sqlite3_prepare_v2(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
    9987             :                 SQLITE_OK)
    9988             :             {
    9989           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    9990             :                          "failed to prepare SQL: %s", pszSQL);
    9991           0 :                 return false;
    9992             :             }
    9993           5 :             sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
    9994           5 :                               static_cast<int>(domainName.size()),
    9995             :                               SQLITE_TRANSIENT);
    9996           5 :             sqlite3_bind_double(hInsertStmt, 2, dfMin);
    9997           5 :             sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
    9998           5 :             sqlite3_bind_double(hInsertStmt, 4, dfMax);
    9999           5 :             sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
   10000           5 :             if (osDescription.empty())
   10001             :             {
   10002           3 :                 sqlite3_bind_null(hInsertStmt, 6);
   10003             :             }
   10004             :             else
   10005             :             {
   10006           2 :                 sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
   10007           2 :                                   static_cast<int>(osDescription.size()),
   10008             :                                   SQLITE_TRANSIENT);
   10009             :             }
   10010           5 :             const int sqlite_err = sqlite3_step(hInsertStmt);
   10011           5 :             sqlite3_finalize(hInsertStmt);
   10012           5 :             if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
   10013             :             {
   10014           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
   10015             :                          "failed to execute insertion: %s",
   10016             :                          sqlite3_errmsg(hDB));
   10017           0 :                 return false;
   10018             :             }
   10019             : 
   10020           5 :             break;
   10021             :         }
   10022             : 
   10023           1 :         case OFDT_GLOB:
   10024             :         {
   10025             :             const auto poGlobDomain =
   10026           1 :                 cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
   10027           2 :             char *pszSQL = sqlite3_mprintf(
   10028             :                 "INSERT INTO gpkg_data_column_constraints ("
   10029             :                 "constraint_name, constraint_type, value, "
   10030             :                 "min, %s, max, %s, "
   10031             :                 "description) VALUES ("
   10032             :                 "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
   10033             :                 min_is_inclusive, max_is_inclusive, domainName.c_str(),
   10034           1 :                 poGlobDomain->GetGlob().c_str(),
   10035           2 :                 osDescription.empty() ? nullptr : osDescription.c_str());
   10036           1 :             bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
   10037           1 :             sqlite3_free(pszSQL);
   10038           1 :             if (!ok)
   10039           0 :                 return false;
   10040             : 
   10041           1 :             break;
   10042             :         }
   10043             :     }
   10044             : 
   10045          17 :     m_oMapFieldDomains[domainName] = std::move(domain);
   10046          17 :     return true;
   10047             : }
   10048             : 
   10049             : /************************************************************************/
   10050             : /*                          AddRelationship()                           */
   10051             : /************************************************************************/
   10052             : 
   10053          20 : bool GDALGeoPackageDataset::AddRelationship(
   10054             :     std::unique_ptr<GDALRelationship> &&relationship,
   10055             :     std::string &failureReason)
   10056             : {
   10057          20 :     if (!GetUpdate())
   10058             :     {
   10059           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10060             :                  "AddRelationship() not supported on read-only dataset");
   10061           0 :         return false;
   10062             :     }
   10063             : 
   10064             :     const std::string osRelationshipName = GenerateNameForRelationship(
   10065          20 :         relationship->GetLeftTableName().c_str(),
   10066          20 :         relationship->GetRightTableName().c_str(),
   10067          80 :         relationship->GetRelatedTableType().c_str());
   10068             :     // sanity checks
   10069          20 :     if (GetRelationship(osRelationshipName) != nullptr)
   10070             :     {
   10071           1 :         failureReason = "A relationship of identical name already exists";
   10072           1 :         return false;
   10073             :     }
   10074             : 
   10075          19 :     if (!ValidateRelationship(relationship.get(), failureReason))
   10076             :     {
   10077          12 :         return false;
   10078             :     }
   10079             : 
   10080           7 :     if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
   10081             :     {
   10082           0 :         return false;
   10083             :     }
   10084           7 :     if (!CreateRelationsTableIfNecessary())
   10085             :     {
   10086           0 :         failureReason = "Could not create gpkgext_relations table";
   10087           0 :         return false;
   10088             :     }
   10089           7 :     if (SQLGetInteger(GetDB(),
   10090             :                       "SELECT 1 FROM gpkg_extensions WHERE "
   10091             :                       "table_name = 'gpkgext_relations'",
   10092           7 :                       nullptr) != 1)
   10093             :     {
   10094           2 :         if (OGRERR_NONE !=
   10095           2 :             SQLCommand(
   10096             :                 GetDB(),
   10097             :                 "INSERT INTO gpkg_extensions "
   10098             :                 "(table_name,column_name,extension_name,definition,scope) "
   10099             :                 "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
   10100             :                 "'http://www.geopackage.org/18-000.html', "
   10101             :                 "'read-write')"))
   10102             :         {
   10103             :             failureReason =
   10104           0 :                 "Could not create gpkg_extensions entry for gpkgext_relations";
   10105           0 :             return false;
   10106             :         }
   10107             :     }
   10108             : 
   10109           7 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10110           7 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10111           7 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10112           7 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10113             : 
   10114          14 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10115           7 :     if (osRelatedTableType.empty())
   10116             :     {
   10117           5 :         osRelatedTableType = "features";
   10118             :     }
   10119             : 
   10120             :     // generate mapping table if not set
   10121          14 :     CPLString osMappingTableName = relationship->GetMappingTableName();
   10122           7 :     if (osMappingTableName.empty())
   10123             :     {
   10124           3 :         int nIndex = 1;
   10125           3 :         osMappingTableName = osLeftTableName + "_" + osRightTableName;
   10126           3 :         while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
   10127             :         {
   10128           0 :             nIndex += 1;
   10129             :             osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
   10130           0 :                                       osRightTableName.c_str(), nIndex);
   10131             :         }
   10132             : 
   10133             :         // determine whether base/related keys are unique
   10134           3 :         bool bBaseKeyIsUnique = false;
   10135             :         {
   10136             :             const std::set<std::string> uniqueBaseFieldsUC =
   10137             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10138           6 :                                                osLeftTableName.c_str());
   10139           6 :             if (uniqueBaseFieldsUC.find(
   10140           3 :                     CPLString(aosLeftTableFields[0]).toupper()) !=
   10141           6 :                 uniqueBaseFieldsUC.end())
   10142             :             {
   10143           2 :                 bBaseKeyIsUnique = true;
   10144             :             }
   10145             :         }
   10146           3 :         bool bRelatedKeyIsUnique = false;
   10147             :         {
   10148             :             const std::set<std::string> uniqueRelatedFieldsUC =
   10149             :                 SQLGetUniqueFieldUCConstraints(GetDB(),
   10150           6 :                                                osRightTableName.c_str());
   10151           6 :             if (uniqueRelatedFieldsUC.find(
   10152           3 :                     CPLString(aosRightTableFields[0]).toupper()) !=
   10153           6 :                 uniqueRelatedFieldsUC.end())
   10154             :             {
   10155           2 :                 bRelatedKeyIsUnique = true;
   10156             :             }
   10157             :         }
   10158             : 
   10159             :         // create mapping table
   10160             : 
   10161           3 :         std::string osBaseIdDefinition = "base_id INTEGER";
   10162           3 :         if (bBaseKeyIsUnique)
   10163             :         {
   10164           2 :             char *pszSQL = sqlite3_mprintf(
   10165             :                 " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10166             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10167             :                 "DEFERRED",
   10168             :                 osMappingTableName.c_str(), osLeftTableName.c_str(),
   10169           2 :                 aosLeftTableFields[0].c_str());
   10170           2 :             osBaseIdDefinition += pszSQL;
   10171           2 :             sqlite3_free(pszSQL);
   10172             :         }
   10173             : 
   10174           3 :         std::string osRelatedIdDefinition = "related_id INTEGER";
   10175           3 :         if (bRelatedKeyIsUnique)
   10176             :         {
   10177           2 :             char *pszSQL = sqlite3_mprintf(
   10178             :                 " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
   10179             :                 "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
   10180             :                 "DEFERRED",
   10181             :                 osMappingTableName.c_str(), osRightTableName.c_str(),
   10182           2 :                 aosRightTableFields[0].c_str());
   10183           2 :             osRelatedIdDefinition += pszSQL;
   10184           2 :             sqlite3_free(pszSQL);
   10185             :         }
   10186             : 
   10187           3 :         char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
   10188             :                                        "id INTEGER PRIMARY KEY AUTOINCREMENT, "
   10189             :                                        "%s, %s);",
   10190             :                                        osMappingTableName.c_str(),
   10191             :                                        osBaseIdDefinition.c_str(),
   10192             :                                        osRelatedIdDefinition.c_str());
   10193           3 :         OGRErr eErr = SQLCommand(hDB, pszSQL);
   10194           3 :         sqlite3_free(pszSQL);
   10195           3 :         if (eErr != OGRERR_NONE)
   10196             :         {
   10197             :             failureReason =
   10198           0 :                 ("Could not create mapping table " + osMappingTableName)
   10199           0 :                     .c_str();
   10200           0 :             return false;
   10201             :         }
   10202             : 
   10203             :         /*
   10204             :          * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
   10205             :          * The related tables extension explicitly states that the mapping table should only be
   10206             :          * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
   10207             :          * https://github.com/opengeospatial/geopackage/issues/679).
   10208             :          *
   10209             :          * However, if we don't insert the mapping table into gpkg_contents then it is no longer
   10210             :          * visible to some clients (eg ESRI software only allows opening tables that are present
   10211             :          * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
   10212             :          *
   10213             :          * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
   10214             :          */
   10215           3 :         pszSQL = sqlite3_mprintf(
   10216             :             "INSERT INTO gpkg_contents "
   10217             :             "(table_name,data_type,identifier,description,last_change,srs_id) "
   10218             :             "VALUES "
   10219             :             "('%q','attributes','%q','Mapping table for relationship between "
   10220             :             "%q and %q',%s,0)",
   10221             :             osMappingTableName.c_str(), /*table_name*/
   10222             :             osMappingTableName.c_str(), /*identifier*/
   10223             :             osLeftTableName.c_str(),    /*description left table name*/
   10224             :             osRightTableName.c_str(),   /*description right table name*/
   10225           6 :             GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
   10226             : 
   10227             :         // Note -- we explicitly ignore failures here, because hey, we aren't really
   10228             :         // supposed to be adding this table to gpkg_contents anyway!
   10229           3 :         (void)SQLCommand(hDB, pszSQL);
   10230           3 :         sqlite3_free(pszSQL);
   10231             : 
   10232           3 :         pszSQL = sqlite3_mprintf(
   10233             :             "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
   10234             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10235           3 :         eErr = SQLCommand(hDB, pszSQL);
   10236           3 :         sqlite3_free(pszSQL);
   10237           3 :         if (eErr != OGRERR_NONE)
   10238             :         {
   10239           0 :             failureReason = ("Could not create index for " +
   10240           0 :                              osMappingTableName + " (base_id)")
   10241           0 :                                 .c_str();
   10242           0 :             return false;
   10243             :         }
   10244             : 
   10245           3 :         pszSQL = sqlite3_mprintf(
   10246             :             "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
   10247             :             osMappingTableName.c_str(), osMappingTableName.c_str());
   10248           3 :         eErr = SQLCommand(hDB, pszSQL);
   10249           3 :         sqlite3_free(pszSQL);
   10250           3 :         if (eErr != OGRERR_NONE)
   10251             :         {
   10252           0 :             failureReason = ("Could not create index for " +
   10253           0 :                              osMappingTableName + " (related_id)")
   10254           0 :                                 .c_str();
   10255           0 :             return false;
   10256             :         }
   10257             :     }
   10258             :     else
   10259             :     {
   10260             :         // validate mapping table structure
   10261           4 :         if (OGRGeoPackageTableLayer *poLayer =
   10262           4 :                 cpl::down_cast<OGRGeoPackageTableLayer *>(
   10263           4 :                     GetLayerByName(osMappingTableName)))
   10264             :         {
   10265           3 :             if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
   10266             :             {
   10267             :                 failureReason =
   10268           2 :                     ("Field base_id must exist in " + osMappingTableName)
   10269           1 :                         .c_str();
   10270           1 :                 return false;
   10271             :             }
   10272           2 :             if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
   10273             :             {
   10274             :                 failureReason =
   10275           2 :                     ("Field related_id must exist in " + osMappingTableName)
   10276           1 :                         .c_str();
   10277           1 :                 return false;
   10278             :             }
   10279             :         }
   10280             :         else
   10281             :         {
   10282             :             failureReason =
   10283           1 :                 ("Could not retrieve table " + osMappingTableName).c_str();
   10284           1 :             return false;
   10285             :         }
   10286             :     }
   10287             : 
   10288           4 :     char *pszSQL = sqlite3_mprintf(
   10289             :         "INSERT INTO gpkg_extensions "
   10290             :         "(table_name,column_name,extension_name,definition,scope) "
   10291             :         "VALUES ('%q', NULL, 'gpkg_related_tables', "
   10292             :         "'http://www.geopackage.org/18-000.html', "
   10293             :         "'read-write')",
   10294             :         osMappingTableName.c_str());
   10295           4 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10296           4 :     sqlite3_free(pszSQL);
   10297           4 :     if (eErr != OGRERR_NONE)
   10298             :     {
   10299           0 :         failureReason = ("Could not insert mapping table " +
   10300           0 :                          osMappingTableName + " into gpkg_extensions")
   10301           0 :                             .c_str();
   10302           0 :         return false;
   10303             :     }
   10304             : 
   10305          12 :     pszSQL = sqlite3_mprintf(
   10306             :         "INSERT INTO gpkgext_relations "
   10307             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10308             :         "primary_column,relation_name,mapping_table_name) "
   10309             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10310           4 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10311           4 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10312             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10313           4 :     eErr = SQLCommand(hDB, pszSQL);
   10314           4 :     sqlite3_free(pszSQL);
   10315           4 :     if (eErr != OGRERR_NONE)
   10316             :     {
   10317           0 :         failureReason = "Could not insert relationship into gpkgext_relations";
   10318           0 :         return false;
   10319             :     }
   10320             : 
   10321           4 :     ClearCachedRelationships();
   10322           4 :     LoadRelationships();
   10323           4 :     return true;
   10324             : }
   10325             : 
   10326             : /************************************************************************/
   10327             : /*                         DeleteRelationship()                         */
   10328             : /************************************************************************/
   10329             : 
   10330           4 : bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
   10331             :                                                std::string &failureReason)
   10332             : {
   10333           4 :     if (eAccess != GA_Update)
   10334             :     {
   10335           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10336             :                  "DeleteRelationship() not supported on read-only dataset");
   10337           0 :         return false;
   10338             :     }
   10339             : 
   10340             :     // ensure relationships are up to date before we try to remove one
   10341           4 :     ClearCachedRelationships();
   10342           4 :     LoadRelationships();
   10343             : 
   10344           8 :     std::string osMappingTableName;
   10345             :     {
   10346           4 :         const GDALRelationship *poRelationship = GetRelationship(name);
   10347           4 :         if (poRelationship == nullptr)
   10348             :         {
   10349           1 :             failureReason = "Could not find relationship with name " + name;
   10350           1 :             return false;
   10351             :         }
   10352             : 
   10353           3 :         osMappingTableName = poRelationship->GetMappingTableName();
   10354             :     }
   10355             : 
   10356             :     // DeleteLayerCommon will delete existing relationship objects, so we can't
   10357             :     // refer to poRelationship or any of its members previously obtained here
   10358           3 :     if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
   10359             :     {
   10360             :         failureReason =
   10361           0 :             "Could not remove mapping layer name " + osMappingTableName;
   10362             : 
   10363             :         // relationships may have been left in an inconsistent state -- reload
   10364             :         // them now
   10365           0 :         ClearCachedRelationships();
   10366           0 :         LoadRelationships();
   10367           0 :         return false;
   10368             :     }
   10369             : 
   10370           3 :     ClearCachedRelationships();
   10371           3 :     LoadRelationships();
   10372           3 :     return true;
   10373             : }
   10374             : 
   10375             : /************************************************************************/
   10376             : /*                        UpdateRelationship()                          */
   10377             : /************************************************************************/
   10378             : 
   10379           6 : bool GDALGeoPackageDataset::UpdateRelationship(
   10380             :     std::unique_ptr<GDALRelationship> &&relationship,
   10381             :     std::string &failureReason)
   10382             : {
   10383           6 :     if (eAccess != GA_Update)
   10384             :     {
   10385           0 :         CPLError(CE_Failure, CPLE_NotSupported,
   10386             :                  "UpdateRelationship() not supported on read-only dataset");
   10387           0 :         return false;
   10388             :     }
   10389             : 
   10390             :     // ensure relationships are up to date before we try to update one
   10391           6 :     ClearCachedRelationships();
   10392           6 :     LoadRelationships();
   10393             : 
   10394           6 :     const std::string &osRelationshipName = relationship->GetName();
   10395           6 :     const std::string &osLeftTableName = relationship->GetLeftTableName();
   10396           6 :     const std::string &osRightTableName = relationship->GetRightTableName();
   10397           6 :     const std::string &osMappingTableName = relationship->GetMappingTableName();
   10398           6 :     const auto &aosLeftTableFields = relationship->GetLeftTableFields();
   10399           6 :     const auto &aosRightTableFields = relationship->GetRightTableFields();
   10400             : 
   10401             :     // sanity checks
   10402             :     {
   10403             :         const GDALRelationship *poExistingRelationship =
   10404           6 :             GetRelationship(osRelationshipName);
   10405           6 :         if (poExistingRelationship == nullptr)
   10406             :         {
   10407             :             failureReason =
   10408           1 :                 "The relationship should already exist to be updated";
   10409           1 :             return false;
   10410             :         }
   10411             : 
   10412           5 :         if (!ValidateRelationship(relationship.get(), failureReason))
   10413             :         {
   10414           2 :             return false;
   10415             :         }
   10416             : 
   10417             :         // we don't permit changes to the participating tables
   10418           3 :         if (osLeftTableName != poExistingRelationship->GetLeftTableName())
   10419             :         {
   10420           0 :             failureReason = ("Cannot change base table from " +
   10421           0 :                              poExistingRelationship->GetLeftTableName() +
   10422           0 :                              " to " + osLeftTableName)
   10423           0 :                                 .c_str();
   10424           0 :             return false;
   10425             :         }
   10426           3 :         if (osRightTableName != poExistingRelationship->GetRightTableName())
   10427             :         {
   10428           0 :             failureReason = ("Cannot change related table from " +
   10429           0 :                              poExistingRelationship->GetRightTableName() +
   10430           0 :                              " to " + osRightTableName)
   10431           0 :                                 .c_str();
   10432           0 :             return false;
   10433             :         }
   10434           3 :         if (osMappingTableName != poExistingRelationship->GetMappingTableName())
   10435             :         {
   10436           0 :             failureReason = ("Cannot change mapping table from " +
   10437           0 :                              poExistingRelationship->GetMappingTableName() +
   10438           0 :                              " to " + osMappingTableName)
   10439           0 :                                 .c_str();
   10440           0 :             return false;
   10441             :         }
   10442             :     }
   10443             : 
   10444           6 :     std::string osRelatedTableType = relationship->GetRelatedTableType();
   10445           3 :     if (osRelatedTableType.empty())
   10446             :     {
   10447           0 :         osRelatedTableType = "features";
   10448             :     }
   10449             : 
   10450           3 :     char *pszSQL = sqlite3_mprintf(
   10451             :         "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
   10452             :         osMappingTableName.c_str());
   10453           3 :     OGRErr eErr = SQLCommand(hDB, pszSQL);
   10454           3 :     sqlite3_free(pszSQL);
   10455           3 :     if (eErr != OGRERR_NONE)
   10456             :     {
   10457             :         failureReason =
   10458           0 :             "Could not delete old relationship from gpkgext_relations";
   10459           0 :         return false;
   10460             :     }
   10461             : 
   10462           9 :     pszSQL = sqlite3_mprintf(
   10463             :         "INSERT INTO gpkgext_relations "
   10464             :         "(base_table_name,base_primary_column,related_table_name,related_"
   10465             :         "primary_column,relation_name,mapping_table_name) "
   10466             :         "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
   10467           3 :         osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
   10468           3 :         osRightTableName.c_str(), aosRightTableFields[0].c_str(),
   10469             :         osRelatedTableType.c_str(), osMappingTableName.c_str());
   10470           3 :     eErr = SQLCommand(hDB, pszSQL);
   10471           3 :     sqlite3_free(pszSQL);
   10472           3 :     if (eErr != OGRERR_NONE)
   10473             :     {
   10474             :         failureReason =
   10475           0 :             "Could not insert updated relationship into gpkgext_relations";
   10476           0 :         return false;
   10477             :     }
   10478             : 
   10479           3 :     ClearCachedRelationships();
   10480           3 :     LoadRelationships();
   10481           3 :     return true;
   10482             : }
   10483             : 
   10484             : /************************************************************************/
   10485             : /*                    GetSqliteMasterContent()                          */
   10486             : /************************************************************************/
   10487             : 
   10488             : const std::vector<SQLSqliteMasterContent> &
   10489           2 : GDALGeoPackageDataset::GetSqliteMasterContent()
   10490             : {
   10491           2 :     if (m_aoSqliteMasterContent.empty())
   10492             :     {
   10493             :         auto oResultTable =
   10494           2 :             SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
   10495           1 :         if (oResultTable)
   10496             :         {
   10497          58 :             for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
   10498             :             {
   10499         114 :                 SQLSqliteMasterContent row;
   10500          57 :                 const char *pszSQL = oResultTable->GetValue(0, rowCnt);
   10501          57 :                 row.osSQL = pszSQL ? pszSQL : "";
   10502          57 :                 const char *pszType = oResultTable->GetValue(1, rowCnt);
   10503          57 :                 row.osType = pszType ? pszType : "";
   10504          57 :                 const char *pszTableName = oResultTable->GetValue(2, rowCnt);
   10505          57 :                 row.osTableName = pszTableName ? pszTableName : "";
   10506          57 :                 m_aoSqliteMasterContent.emplace_back(std::move(row));
   10507             :             }
   10508             :         }
   10509             :     }
   10510           2 :     return m_aoSqliteMasterContent;
   10511             : }

Generated by: LCOV version 1.14