Coverage Report

Created: 2025-06-09 08:44

/src/gdal/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GeoPackage Translator
4
 * Purpose:  Implements GDALGeoPackageDataset class
5
 * Author:   Paul Ramsey <pramsey@boundlessgeo.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
9
 * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogr_geopackage.h"
15
#include "ogr_p.h"
16
#include "ogr_swq.h"
17
#include "gdalwarper.h"
18
#include "gdal_utils.h"
19
#include "ogrgeopackageutility.h"
20
#include "ogrsqliteutility.h"
21
#include "ogr_wkb.h"
22
#include "vrt/vrtdataset.h"
23
24
#include "tilematrixset.hpp"
25
26
#include <cstdlib>
27
28
#include <algorithm>
29
#include <limits>
30
#include <sstream>
31
32
#define COMPILATION_ALLOWED
33
#define DEFINE_OGRSQLiteSQLFunctionsSetCaseSensitiveLike
34
#include "ogrsqlitesqlfunctionscommon.cpp"
35
36
// Keep in sync prototype of those 2 functions between gdalopeninfo.cpp,
37
// ogrsqlitedatasource.cpp and ogrgeopackagedatasource.cpp
38
void GDALOpenInfoDeclareFileNotToOpen(const char *pszFilename,
39
                                      const GByte *pabyHeader,
40
                                      int nHeaderBytes);
41
void GDALOpenInfoUnDeclareFileNotToOpen(const char *pszFilename);
42
43
/************************************************************************/
44
/*                             Tiling schemes                           */
45
/************************************************************************/
46
47
typedef struct
48
{
49
    const char *pszName;
50
    int nEPSGCode;
51
    double dfMinX;
52
    double dfMaxY;
53
    int nTileXCountZoomLevel0;
54
    int nTileYCountZoomLevel0;
55
    int nTileWidth;
56
    int nTileHeight;
57
    double dfPixelXSizeZoomLevel0;
58
    double dfPixelYSizeZoomLevel0;
59
} TilingSchemeDefinition;
60
61
static const TilingSchemeDefinition asTilingSchemes[] = {
62
    /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
63
       Annex E.3 */
64
    {"GoogleCRS84Quad", 4326, -180.0, 180.0, 1, 1, 256, 256, 360.0 / 256,
65
     360.0 / 256},
66
67
    /* See global-mercator at
68
       http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
69
    {"PseudoTMS_GlobalMercator", 3857, -20037508.34, 20037508.34, 2, 2, 256,
70
     256, 78271.516, 78271.516},
71
};
72
73
// Setting it above 30 would lead to integer overflow ((1 << 31) > INT_MAX)
74
constexpr int MAX_ZOOM_LEVEL = 30;
75
76
/************************************************************************/
77
/*                     GetTilingScheme()                                */
78
/************************************************************************/
79
80
static std::unique_ptr<TilingSchemeDefinition>
81
GetTilingScheme(const char *pszName)
82
0
{
83
0
    if (EQUAL(pszName, "CUSTOM"))
84
0
        return nullptr;
85
86
0
    for (const auto &tilingScheme : asTilingSchemes)
87
0
    {
88
0
        if (EQUAL(pszName, tilingScheme.pszName))
89
0
        {
90
0
            return std::make_unique<TilingSchemeDefinition>(tilingScheme);
91
0
        }
92
0
    }
93
94
0
    if (EQUAL(pszName, "PseudoTMS_GlobalGeodetic"))
95
0
        pszName = "InspireCRS84Quad";
96
97
0
    auto poTM = gdal::TileMatrixSet::parse(pszName);
98
0
    if (poTM == nullptr)
99
0
        return nullptr;
100
0
    if (!poTM->haveAllLevelsSameTopLeft())
101
0
    {
102
0
        CPLError(CE_Failure, CPLE_NotSupported,
103
0
                 "Unsupported tiling scheme: not all zoom levels have same top "
104
0
                 "left corner");
105
0
        return nullptr;
106
0
    }
107
0
    if (!poTM->haveAllLevelsSameTileSize())
108
0
    {
109
0
        CPLError(CE_Failure, CPLE_NotSupported,
110
0
                 "Unsupported tiling scheme: not all zoom levels have same "
111
0
                 "tile size");
112
0
        return nullptr;
113
0
    }
114
0
    if (!poTM->hasOnlyPowerOfTwoVaryingScales())
115
0
    {
116
0
        CPLError(CE_Failure, CPLE_NotSupported,
117
0
                 "Unsupported tiling scheme: resolution of consecutive zoom "
118
0
                 "levels is not always 2");
119
0
        return nullptr;
120
0
    }
121
0
    if (poTM->hasVariableMatrixWidth())
122
0
    {
123
0
        CPLError(CE_Failure, CPLE_NotSupported,
124
0
                 "Unsupported tiling scheme: some levels have variable matrix "
125
0
                 "width");
126
0
        return nullptr;
127
0
    }
128
0
    auto poTilingScheme = std::make_unique<TilingSchemeDefinition>();
129
0
    poTilingScheme->pszName = pszName;
130
131
0
    OGRSpatialReference oSRS;
132
0
    if (oSRS.SetFromUserInput(poTM->crs().c_str()) != OGRERR_NONE)
133
0
    {
134
0
        return nullptr;
135
0
    }
136
0
    if (poTM->crs() == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
137
0
    {
138
0
        poTilingScheme->nEPSGCode = 4326;
139
0
    }
140
0
    else
141
0
    {
142
0
        const char *pszAuthName = oSRS.GetAuthorityName(nullptr);
143
0
        const char *pszAuthCode = oSRS.GetAuthorityCode(nullptr);
144
0
        if (pszAuthName == nullptr || !EQUAL(pszAuthName, "EPSG") ||
145
0
            pszAuthCode == nullptr)
146
0
        {
147
0
            CPLError(CE_Failure, CPLE_NotSupported,
148
0
                     "Unsupported tiling scheme: only EPSG CRS supported");
149
0
            return nullptr;
150
0
        }
151
0
        poTilingScheme->nEPSGCode = atoi(pszAuthCode);
152
0
    }
153
0
    const auto &zoomLevel0 = poTM->tileMatrixList()[0];
154
0
    poTilingScheme->dfMinX = zoomLevel0.mTopLeftX;
155
0
    poTilingScheme->dfMaxY = zoomLevel0.mTopLeftY;
156
0
    poTilingScheme->nTileXCountZoomLevel0 = zoomLevel0.mMatrixWidth;
157
0
    poTilingScheme->nTileYCountZoomLevel0 = zoomLevel0.mMatrixHeight;
158
0
    poTilingScheme->nTileWidth = zoomLevel0.mTileWidth;
159
0
    poTilingScheme->nTileHeight = zoomLevel0.mTileHeight;
160
0
    poTilingScheme->dfPixelXSizeZoomLevel0 = zoomLevel0.mResX;
161
0
    poTilingScheme->dfPixelYSizeZoomLevel0 = zoomLevel0.mResY;
162
163
0
    const bool bInvertAxis = oSRS.EPSGTreatsAsLatLong() != FALSE ||
164
0
                             oSRS.EPSGTreatsAsNorthingEasting() != FALSE;
165
0
    if (bInvertAxis)
166
0
    {
167
0
        std::swap(poTilingScheme->dfMinX, poTilingScheme->dfMaxY);
168
0
        std::swap(poTilingScheme->dfPixelXSizeZoomLevel0,
169
0
                  poTilingScheme->dfPixelYSizeZoomLevel0);
170
0
    }
171
0
    return poTilingScheme;
172
0
}
173
174
static const char *pszCREATE_GPKG_GEOMETRY_COLUMNS =
175
    "CREATE TABLE gpkg_geometry_columns ("
176
    "table_name TEXT NOT NULL,"
177
    "column_name TEXT NOT NULL,"
178
    "geometry_type_name TEXT NOT NULL,"
179
    "srs_id INTEGER NOT NULL,"
180
    "z TINYINT NOT NULL,"
181
    "m TINYINT NOT NULL,"
182
    "CONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name),"
183
    "CONSTRAINT uk_gc_table_name UNIQUE (table_name),"
184
    "CONSTRAINT fk_gc_tn FOREIGN KEY (table_name) REFERENCES "
185
    "gpkg_contents(table_name),"
186
    "CONSTRAINT fk_gc_srs FOREIGN KEY (srs_id) REFERENCES gpkg_spatial_ref_sys "
187
    "(srs_id)"
188
    ")";
189
190
OGRErr GDALGeoPackageDataset::SetApplicationAndUserVersionId()
191
255
{
192
255
    CPLAssert(hDB != nullptr);
193
194
255
    const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
195
255
                                                "PRAGMA user_version = %u",
196
255
                                                m_nApplicationId,
197
255
                                                m_nUserVersion));
198
255
    return SQLCommand(hDB, osPragma.c_str());
199
255
}
200
201
bool GDALGeoPackageDataset::CloseDB()
202
2.56k
{
203
2.56k
    OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
204
2.56k
    m_pSQLFunctionData = nullptr;
205
2.56k
    return OGRSQLiteBaseDataSource::CloseDB();
206
2.56k
}
207
208
bool GDALGeoPackageDataset::ReOpenDB()
209
0
{
210
0
    CPLAssert(hDB != nullptr);
211
0
    CPLAssert(m_pszFilename != nullptr);
212
213
0
    FinishSpatialite();
214
215
0
    CloseDB();
216
217
    /* And re-open the file */
218
0
    return OpenOrCreateDB(SQLITE_OPEN_READWRITE);
219
0
}
220
221
static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef,
222
                                     int nEPSGCode)
223
146
{
224
146
    CPLPushErrorHandler(CPLQuietErrorHandler);
225
146
    const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
226
146
    CPLPopErrorHandler();
227
146
    CPLErrorReset();
228
146
    return eErr;
229
146
}
230
231
std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
232
GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
233
                                     bool bEmitErrorIfNotFound)
234
265
{
235
265
    const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
236
265
    if (oIter != m_oMapSrsIdToSrs.end())
237
18
    {
238
18
        if (oIter->second == nullptr)
239
17
            return nullptr;
240
1
        oIter->second->Reference();
241
1
        return std::unique_ptr<OGRSpatialReference,
242
1
                               OGRSpatialReferenceReleaser>(oIter->second);
243
18
    }
244
245
247
    if (iSrsId == 0 || iSrsId == -1)
246
15
    {
247
15
        OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
248
15
        poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
249
250
        // See corresponding tests in GDALGeoPackageDataset::GetSrsId
251
15
        if (iSrsId == 0)
252
15
        {
253
15
            poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
254
15
                                    "unknown", SRS_WGS84_SEMIMAJOR,
255
15
                                    SRS_WGS84_INVFLATTENING);
256
15
        }
257
0
        else if (iSrsId == -1)
258
0
        {
259
0
            poSpatialRef->SetLocalCS("Undefined Cartesian SRS");
260
0
            poSpatialRef->SetLinearUnits(SRS_UL_METER, 1.0);
261
0
        }
262
263
15
        m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
264
15
        poSpatialRef->Reference();
265
15
        return std::unique_ptr<OGRSpatialReference,
266
15
                               OGRSpatialReferenceReleaser>(poSpatialRef);
267
15
    }
268
269
232
    CPLString oSQL;
270
232
    oSQL.Printf("SELECT srs_name, definition, organization, "
271
232
                "organization_coordsys_id%s%s "
272
232
                "FROM gpkg_spatial_ref_sys WHERE "
273
232
                "srs_id = %d LIMIT 2",
274
232
                m_bHasDefinition12_063 ? ", definition_12_063" : "",
275
232
                m_bHasEpochColumn ? ", epoch" : "", iSrsId);
276
277
232
    auto oResult = SQLQuery(hDB, oSQL.c_str());
278
279
232
    if (!oResult || oResult->RowCount() != 1)
280
131
    {
281
131
        if (bFallbackToEPSG)
282
0
        {
283
0
            CPLDebug("GPKG",
284
0
                     "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
285
0
                     iSrsId);
286
0
            OGRSpatialReference *poSRS = new OGRSpatialReference();
287
0
            if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE)
288
0
            {
289
0
                poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
290
0
                return std::unique_ptr<OGRSpatialReference,
291
0
                                       OGRSpatialReferenceReleaser>(poSRS);
292
0
            }
293
0
            poSRS->Release();
294
0
        }
295
131
        else if (bEmitErrorIfNotFound)
296
131
        {
297
131
            CPLError(CE_Warning, CPLE_AppDefined,
298
131
                     "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
299
131
                     iSrsId);
300
131
            m_oMapSrsIdToSrs[iSrsId] = nullptr;
301
131
        }
302
131
        return nullptr;
303
131
    }
304
305
101
    const char *pszName = oResult->GetValue(0, 0);
306
101
    if (pszName && EQUAL(pszName, "Undefined SRS"))
307
1
    {
308
1
        m_oMapSrsIdToSrs[iSrsId] = nullptr;
309
1
        return nullptr;
310
1
    }
311
100
    const char *pszWkt = oResult->GetValue(1, 0);
312
100
    if (pszWkt == nullptr)
313
0
        return nullptr;
314
100
    const char *pszOrganization = oResult->GetValue(2, 0);
315
100
    const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
316
100
    const char *pszWkt2 =
317
100
        m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
318
100
    if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
319
0
        pszWkt = pszWkt2;
320
100
    const char *pszCoordinateEpoch =
321
100
        m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
322
100
    const double dfCoordinateEpoch =
323
100
        pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
324
325
100
    OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
326
100
    poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
327
    // Try to import first from EPSG code, and then from WKT
328
100
    if (!(pszOrganization && pszOrganizationCoordsysID &&
329
100
          EQUAL(pszOrganization, "EPSG") &&
330
100
          (atoi(pszOrganizationCoordsysID) == iSrsId ||
331
95
           (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
332
100
          GDALGPKGImportFromEPSG(
333
95
              poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
334
100
        poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
335
3
    {
336
3
        CPLError(CE_Warning, CPLE_AppDefined,
337
3
                 "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
338
3
                 pszWkt);
339
3
        delete poSpatialRef;
340
3
        m_oMapSrsIdToSrs[iSrsId] = nullptr;
341
3
        return nullptr;
342
3
    }
343
344
97
    poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
345
97
    poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
346
97
    m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
347
97
    poSpatialRef->Reference();
348
97
    return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
349
97
        poSpatialRef);
350
100
}
351
352
const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
353
51
{
354
51
    const char *pszName = oSRS.GetName();
355
51
    if (pszName)
356
51
        return pszName;
357
358
    // Something odd.  Return empty.
359
0
    return "Unnamed SRS";
360
51
}
361
362
/* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
363
bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
364
    bool bForceEpoch)
365
0
{
366
0
    const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
367
0
    auto oResultTable = SQLQuery(
368
0
        hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
369
0
             "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
370
0
    if (!oResultTable)
371
0
        return false;
372
373
    // Temporary remove foreign key checks
374
0
    const GPKGTemporaryForeignKeyCheckDisabler
375
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
376
377
0
    bool bRet = SoftStartTransaction() == OGRERR_NONE;
378
379
0
    if (bRet)
380
0
    {
381
0
        std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
382
0
                          "srs_name TEXT NOT NULL,"
383
0
                          "srs_id INTEGER NOT NULL PRIMARY KEY,"
384
0
                          "organization TEXT NOT NULL,"
385
0
                          "organization_coordsys_id INTEGER NOT NULL,"
386
0
                          "definition TEXT NOT NULL,"
387
0
                          "description TEXT, "
388
0
                          "definition_12_063 TEXT NOT NULL");
389
0
        if (bAddEpoch)
390
0
            osSQL += ", epoch DOUBLE";
391
0
        osSQL += ")";
392
0
        bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
393
0
    }
394
395
0
    if (bRet)
396
0
    {
397
0
        for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
398
0
        {
399
0
            const char *pszSrsName = oResultTable->GetValue(0, i);
400
0
            const char *pszSrsId = oResultTable->GetValue(1, i);
401
0
            const char *pszOrganization = oResultTable->GetValue(2, i);
402
0
            const char *pszOrganizationCoordsysID =
403
0
                oResultTable->GetValue(3, i);
404
0
            const char *pszDefinition = oResultTable->GetValue(4, i);
405
0
            if (pszSrsName == nullptr || pszSrsId == nullptr ||
406
0
                pszOrganization == nullptr ||
407
0
                pszOrganizationCoordsysID == nullptr)
408
0
            {
409
                // should not happen as there are NOT NULL constraints
410
                // But a database could lack such NOT NULL constraints or have
411
                // large values that would cause a memory allocation failure.
412
0
            }
413
0
            const char *pszDescription = oResultTable->GetValue(5, i);
414
0
            char *pszSQL;
415
416
0
            OGRSpatialReference oSRS;
417
0
            if (pszOrganization && pszOrganizationCoordsysID &&
418
0
                EQUAL(pszOrganization, "EPSG"))
419
0
            {
420
0
                oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
421
0
            }
422
0
            if (!oSRS.IsEmpty() && pszDefinition &&
423
0
                !EQUAL(pszDefinition, "undefined"))
424
0
            {
425
0
                oSRS.SetFromUserInput(pszDefinition);
426
0
            }
427
0
            char *pszWKT2 = nullptr;
428
0
            if (!oSRS.IsEmpty())
429
0
            {
430
0
                const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
431
0
                                                       nullptr};
432
0
                oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
433
0
                if (pszWKT2 && pszWKT2[0] == '\0')
434
0
                {
435
0
                    CPLFree(pszWKT2);
436
0
                    pszWKT2 = nullptr;
437
0
                }
438
0
            }
439
0
            if (pszWKT2 == nullptr)
440
0
            {
441
0
                pszWKT2 = CPLStrdup("undefined");
442
0
            }
443
444
0
            if (pszDescription)
445
0
            {
446
0
                pszSQL = sqlite3_mprintf(
447
0
                    "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
448
0
                    "organization, organization_coordsys_id, definition, "
449
0
                    "description, definition_12_063) VALUES ('%q', '%q', '%q', "
450
0
                    "'%q', '%q', '%q', '%q')",
451
0
                    pszSrsName, pszSrsId, pszOrganization,
452
0
                    pszOrganizationCoordsysID, pszDefinition, pszDescription,
453
0
                    pszWKT2);
454
0
            }
455
0
            else
456
0
            {
457
0
                pszSQL = sqlite3_mprintf(
458
0
                    "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
459
0
                    "organization, organization_coordsys_id, definition, "
460
0
                    "description, definition_12_063) VALUES ('%q', '%q', '%q', "
461
0
                    "'%q', '%q', NULL, '%q')",
462
0
                    pszSrsName, pszSrsId, pszOrganization,
463
0
                    pszOrganizationCoordsysID, pszDefinition, pszWKT2);
464
0
            }
465
466
0
            CPLFree(pszWKT2);
467
0
            bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
468
0
            sqlite3_free(pszSQL);
469
0
        }
470
0
    }
471
472
0
    if (bRet)
473
0
    {
474
0
        bRet =
475
0
            SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
476
0
    }
477
0
    if (bRet)
478
0
    {
479
0
        bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
480
0
                               "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
481
0
    }
482
0
    if (bRet)
483
0
    {
484
0
        bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
485
0
               OGRERR_NONE == SQLCommand(hDB,
486
0
                                         "INSERT INTO gpkg_extensions "
487
0
                                         "(table_name, column_name, "
488
0
                                         "extension_name, definition, scope) "
489
0
                                         "VALUES "
490
0
                                         "('gpkg_spatial_ref_sys', "
491
0
                                         "'definition_12_063', 'gpkg_crs_wkt', "
492
0
                                         "'http://www.geopackage.org/spec120/"
493
0
                                         "#extension_crs_wkt', 'read-write')");
494
0
    }
495
0
    if (bRet && bAddEpoch)
496
0
    {
497
0
        bRet =
498
0
            OGRERR_NONE ==
499
0
                SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
500
0
                                "'gpkg_crs_wkt_1_1' "
501
0
                                "WHERE extension_name = 'gpkg_crs_wkt'") &&
502
0
            OGRERR_NONE ==
503
0
                SQLCommand(
504
0
                    hDB,
505
0
                    "INSERT INTO gpkg_extensions "
506
0
                    "(table_name, column_name, extension_name, definition, "
507
0
                    "scope) "
508
0
                    "VALUES "
509
0
                    "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
510
0
                    "'http://www.geopackage.org/spec/#extension_crs_wkt', "
511
0
                    "'read-write')");
512
0
    }
513
0
    if (bRet)
514
0
    {
515
0
        SoftCommitTransaction();
516
0
        m_bHasDefinition12_063 = true;
517
0
        if (bAddEpoch)
518
0
            m_bHasEpochColumn = true;
519
0
    }
520
0
    else
521
0
    {
522
0
        SoftRollbackTransaction();
523
0
    }
524
525
0
    return bRet;
526
0
}
527
528
int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
529
559
{
530
559
    const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
531
559
    if (!poSRSIn || poSRSIn->IsEmpty() ||
532
559
        (pszName && EQUAL(pszName, "Undefined SRS")))
533
422
    {
534
422
        OGRErr err = OGRERR_NONE;
535
422
        const int nSRSId = SQLGetInteger(
536
422
            hDB,
537
422
            "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
538
422
            "'Undefined SRS' AND organization = 'GDAL'",
539
422
            &err);
540
422
        if (err == OGRERR_NONE)
541
390
            return nSRSId;
542
543
        // The below WKT definitions are somehow questionable (using a unknown
544
        // unit). For GDAL >= 3.9, they won't be used. They will only be used
545
        // for earlier versions.
546
32
        const char *pszSQL;
547
32
#define UNDEFINED_CRS_SRS_ID 99999
548
32
        static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
549
32
#define STRINGIFY(x) #x
550
32
#define XSTRINGIFY(x) STRINGIFY(x)
551
32
        if (m_bHasDefinition12_063)
552
0
        {
553
            /* clang-format off */
554
0
            pszSQL =
555
0
                "INSERT INTO gpkg_spatial_ref_sys "
556
0
                "(srs_name,srs_id,organization,organization_coordsys_id,"
557
0
                "definition, definition_12_063, description) VALUES "
558
0
                "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
559
0
                XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
560
0
                "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
561
0
                "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
562
0
                "AXIS[\"Northing\",NORTH]]',"
563
0
                "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
564
0
                "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
565
0
                "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
566
0
                "'Custom undefined coordinate reference system')";
567
            /* clang-format on */
568
0
        }
569
32
        else
570
32
        {
571
            /* clang-format off */
572
32
            pszSQL =
573
32
                "INSERT INTO gpkg_spatial_ref_sys "
574
32
                "(srs_name,srs_id,organization,organization_coordsys_id,"
575
32
                "definition, description) VALUES "
576
32
                "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
577
32
                XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
578
32
                "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
579
32
                "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
580
32
                "AXIS[\"Northing\",NORTH]]',"
581
32
                "'Custom undefined coordinate reference system')";
582
            /* clang-format on */
583
32
        }
584
32
        if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
585
32
            return UNDEFINED_CRS_SRS_ID;
586
0
#undef UNDEFINED_CRS_SRS_ID
587
0
#undef XSTRINGIFY
588
0
#undef STRINGIFY
589
0
        return -1;
590
32
    }
591
592
137
    std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
593
594
137
    if (poSRS->IsGeographic() || poSRS->IsLocal())
595
84
    {
596
        // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
597
84
        if (pszName != nullptr && strlen(pszName) > 0)
598
84
        {
599
84
            if (EQUAL(pszName, "Undefined geographic SRS"))
600
0
                return 0;
601
602
84
            if (EQUAL(pszName, "Undefined Cartesian SRS"))
603
0
                return -1;
604
84
        }
605
84
    }
606
607
137
    const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
608
609
137
    if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
610
0
    {
611
        // Try to force identify an EPSG code.
612
0
        poSRS->AutoIdentifyEPSG();
613
614
0
        pszAuthorityName = poSRS->GetAuthorityName(nullptr);
615
0
        if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
616
0
        {
617
0
            const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
618
0
            if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
619
0
            {
620
                /* Import 'clean' SRS */
621
0
                poSRS->importFromEPSG(atoi(pszAuthorityCode));
622
623
0
                pszAuthorityName = poSRS->GetAuthorityName(nullptr);
624
0
            }
625
0
        }
626
627
0
        poSRS->SetCoordinateEpoch(poSRSIn->GetCoordinateEpoch());
628
0
    }
629
630
    // Check whether the EPSG authority code is already mapped to a
631
    // SRS ID.
632
137
    char *pszSQL = nullptr;
633
137
    int nSRSId = DEFAULT_SRID;
634
137
    int nAuthorityCode = 0;
635
137
    OGRErr err = OGRERR_NONE;
636
137
    bool bCanUseAuthorityCode = false;
637
137
    const char *const apszIsSameOptions[] = {
638
137
        "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
639
137
        "IGNORE_COORDINATE_EPOCH=YES", nullptr};
640
137
    if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
641
137
    {
642
137
        const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
643
137
        if (pszAuthorityCode)
644
137
        {
645
137
            if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
646
137
            {
647
137
                nAuthorityCode = atoi(pszAuthorityCode);
648
137
            }
649
0
            else
650
0
            {
651
0
                CPLDebug("GPKG",
652
0
                         "SRS has %s:%s identification, but the code not "
653
0
                         "being an integer value cannot be stored as such "
654
0
                         "in the database.",
655
0
                         pszAuthorityName, pszAuthorityCode);
656
0
                pszAuthorityName = nullptr;
657
0
            }
658
137
        }
659
137
    }
660
661
137
    if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
662
137
        poSRSIn->GetCoordinateEpoch() == 0)
663
137
    {
664
137
        pszSQL =
665
137
            sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
666
137
                            "upper(organization) = upper('%q') AND "
667
137
                            "organization_coordsys_id = %d",
668
137
                            pszAuthorityName, nAuthorityCode);
669
670
137
        nSRSId = SQLGetInteger(hDB, pszSQL, &err);
671
137
        sqlite3_free(pszSQL);
672
673
        // Got a match? Return it!
674
137
        if (OGRERR_NONE == err)
675
86
        {
676
86
            auto poRefSRS = GetSpatialRef(nSRSId);
677
86
            bool bOK =
678
86
                (poRefSRS == nullptr ||
679
86
                 poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
680
86
                 !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
681
86
            if (bOK)
682
86
            {
683
86
                return nSRSId;
684
86
            }
685
0
            else
686
0
            {
687
0
                CPLError(CE_Warning, CPLE_AppDefined,
688
0
                         "Passed SRS uses %s:%d identification, but its "
689
0
                         "definition is not compatible with the "
690
0
                         "definition of that object already in the database. "
691
0
                         "Registering it as a new entry into the database.",
692
0
                         pszAuthorityName, nAuthorityCode);
693
0
                pszAuthorityName = nullptr;
694
0
                nAuthorityCode = 0;
695
0
            }
696
86
        }
697
137
    }
698
699
    // Translate SRS to WKT.
700
51
    CPLCharUniquePtr pszWKT1;
701
51
    CPLCharUniquePtr pszWKT2_2015;
702
51
    CPLCharUniquePtr pszWKT2_2019;
703
51
    const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
704
51
    const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
705
51
    const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
706
707
51
    std::string osEpochTest;
708
51
    if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
709
0
    {
710
0
        osEpochTest =
711
0
            CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
712
0
    }
713
714
51
    if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3))
715
51
    {
716
51
        char *pszTmp = nullptr;
717
51
        poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
718
51
        pszWKT1.reset(pszTmp);
719
51
        if (pszWKT1 && pszWKT1.get()[0] == '\0')
720
0
        {
721
0
            pszWKT1.reset();
722
0
        }
723
51
    }
724
51
    {
725
51
        char *pszTmp = nullptr;
726
51
        poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
727
51
        pszWKT2_2015.reset(pszTmp);
728
51
        if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
729
0
        {
730
0
            pszWKT2_2015.reset();
731
0
        }
732
51
    }
733
51
    {
734
51
        char *pszTmp = nullptr;
735
51
        poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
736
51
        pszWKT2_2019.reset(pszTmp);
737
51
        if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
738
0
        {
739
0
            pszWKT2_2019.reset();
740
0
        }
741
51
    }
742
743
51
    if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
744
0
    {
745
0
        return DEFAULT_SRID;
746
0
    }
747
748
51
    if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
749
51
    {
750
        // Search if there is already an existing entry with this WKT
751
51
        if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
752
0
        {
753
0
            if (pszWKT1)
754
0
            {
755
0
                pszSQL = sqlite3_mprintf(
756
0
                    "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
757
0
                    "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
758
0
                    pszWKT1.get(),
759
0
                    pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
760
0
                    pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
761
0
                    osEpochTest.c_str());
762
0
            }
763
0
            else
764
0
            {
765
0
                pszSQL = sqlite3_mprintf(
766
0
                    "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
767
0
                    "definition_12_063 IN ('%q', '%q')%s",
768
0
                    pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
769
0
                    pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
770
0
                    osEpochTest.c_str());
771
0
            }
772
0
        }
773
51
        else if (pszWKT1)
774
51
        {
775
51
            pszSQL =
776
51
                sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
777
51
                                "definition = '%q'%s",
778
51
                                pszWKT1.get(), osEpochTest.c_str());
779
51
        }
780
0
        else
781
0
        {
782
0
            pszSQL = nullptr;
783
0
        }
784
51
        if (pszSQL)
785
51
        {
786
51
            nSRSId = SQLGetInteger(hDB, pszSQL, &err);
787
51
            sqlite3_free(pszSQL);
788
51
            if (OGRERR_NONE == err)
789
0
            {
790
0
                return nSRSId;
791
0
            }
792
51
        }
793
51
    }
794
795
51
    if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
796
51
        poSRSIn->GetCoordinateEpoch() == 0)
797
51
    {
798
51
        bool bTryToReuseSRSId = true;
799
51
        if (EQUAL(pszAuthorityName, "EPSG"))
800
51
        {
801
51
            OGRSpatialReference oSRS_EPSG;
802
51
            if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
803
51
                OGRERR_NONE)
804
51
            {
805
51
                if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
806
51
                    CPLTestBool(
807
0
                        CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
808
0
                {
809
0
                    bTryToReuseSRSId = false;
810
0
                    CPLError(
811
0
                        CE_Warning, CPLE_AppDefined,
812
0
                        "Passed SRS uses %s:%d identification, but its "
813
0
                        "definition is not compatible with the "
814
0
                        "official definition of the object. "
815
0
                        "Registering it as a non-%s entry into the database.",
816
0
                        pszAuthorityName, nAuthorityCode, pszAuthorityName);
817
0
                    pszAuthorityName = nullptr;
818
0
                    nAuthorityCode = 0;
819
0
                }
820
51
            }
821
51
        }
822
51
        if (bTryToReuseSRSId)
823
51
        {
824
            // No match, but maybe we can use the nAuthorityCode as the nSRSId?
825
51
            pszSQL = sqlite3_mprintf(
826
51
                "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
827
51
                "srs_id = %d",
828
51
                nAuthorityCode);
829
830
            // Yep, we can!
831
51
            if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
832
51
                bCanUseAuthorityCode = true;
833
51
            sqlite3_free(pszSQL);
834
51
        }
835
51
    }
836
837
51
    bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
838
51
    bool bForceEpoch = false;
839
51
    if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
840
51
        (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
841
0
    {
842
0
        bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
843
0
    }
844
845
    // Add epoch column if needed
846
51
    if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
847
0
    {
848
0
        if (m_bHasDefinition12_063)
849
0
        {
850
0
            if (SoftStartTransaction() != OGRERR_NONE)
851
0
                return DEFAULT_SRID;
852
0
            if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
853
0
                                "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
854
0
                SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
855
0
                                "'gpkg_crs_wkt_1_1' "
856
0
                                "WHERE extension_name = 'gpkg_crs_wkt'") !=
857
0
                    OGRERR_NONE ||
858
0
                SQLCommand(
859
0
                    hDB,
860
0
                    "INSERT INTO gpkg_extensions "
861
0
                    "(table_name, column_name, extension_name, definition, "
862
0
                    "scope) "
863
0
                    "VALUES "
864
0
                    "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
865
0
                    "'http://www.geopackage.org/spec/#extension_crs_wkt', "
866
0
                    "'read-write')") != OGRERR_NONE)
867
0
            {
868
0
                SoftRollbackTransaction();
869
0
                return DEFAULT_SRID;
870
0
            }
871
872
0
            if (SoftCommitTransaction() != OGRERR_NONE)
873
0
                return DEFAULT_SRID;
874
875
0
            m_bHasEpochColumn = true;
876
0
        }
877
0
        else
878
0
        {
879
0
            bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
880
0
            bForceEpoch = true;
881
0
        }
882
0
    }
883
884
51
    if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
885
51
        !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
886
0
    {
887
0
        return DEFAULT_SRID;
888
0
    }
889
890
    // Reuse the authority code number as SRS_ID if we can
891
51
    if (bCanUseAuthorityCode)
892
51
    {
893
51
        nSRSId = nAuthorityCode;
894
51
    }
895
    // Otherwise, generate a new SRS_ID number (max + 1)
896
0
    else
897
0
    {
898
        // Get the current maximum srid in the srs table.
899
0
        const int nMaxSRSId = SQLGetInteger(
900
0
            hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
901
0
        nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
902
0
    }
903
904
51
    std::string osEpochColumn;
905
51
    std::string osEpochVal;
906
51
    if (poSRSIn->GetCoordinateEpoch() > 0)
907
0
    {
908
0
        osEpochColumn = ", epoch";
909
0
        osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
910
0
    }
911
912
    // Add new SRS row to gpkg_spatial_ref_sys.
913
51
    if (m_bHasDefinition12_063)
914
0
    {
915
        // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
916
0
        const char *pszWKT2 = poSRSIn->IsDynamic() &&
917
0
                                      poSRSIn->GetCoordinateEpoch() > 0 &&
918
0
                                      pszWKT2_2019
919
0
                                  ? pszWKT2_2019.get()
920
0
                              : pszWKT2_2015 ? pszWKT2_2015.get()
921
0
                                             : pszWKT2_2019.get();
922
923
0
        if (pszAuthorityName != nullptr && nAuthorityCode > 0)
924
0
        {
925
0
            pszSQL = sqlite3_mprintf(
926
0
                "INSERT INTO gpkg_spatial_ref_sys "
927
0
                "(srs_name,srs_id,organization,organization_coordsys_id,"
928
0
                "definition, definition_12_063%s) VALUES "
929
0
                "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
930
0
                osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
931
0
                pszAuthorityName, nAuthorityCode,
932
0
                pszWKT1 ? pszWKT1.get() : "undefined",
933
0
                pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
934
0
        }
935
0
        else
936
0
        {
937
0
            pszSQL = sqlite3_mprintf(
938
0
                "INSERT INTO gpkg_spatial_ref_sys "
939
0
                "(srs_name,srs_id,organization,organization_coordsys_id,"
940
0
                "definition, definition_12_063%s) VALUES "
941
0
                "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
942
0
                osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
943
0
                nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
944
0
                pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
945
0
        }
946
0
    }
947
51
    else
948
51
    {
949
51
        if (pszAuthorityName != nullptr && nAuthorityCode > 0)
950
51
        {
951
51
            pszSQL = sqlite3_mprintf(
952
51
                "INSERT INTO gpkg_spatial_ref_sys "
953
51
                "(srs_name,srs_id,organization,organization_coordsys_id,"
954
51
                "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
955
51
                GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
956
51
                pszWKT1 ? pszWKT1.get() : "undefined");
957
51
        }
958
0
        else
959
0
        {
960
0
            pszSQL = sqlite3_mprintf(
961
0
                "INSERT INTO gpkg_spatial_ref_sys "
962
0
                "(srs_name,srs_id,organization,organization_coordsys_id,"
963
0
                "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
964
0
                GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
965
0
                pszWKT1 ? pszWKT1.get() : "undefined");
966
0
        }
967
51
    }
968
969
    // Add new row to gpkg_spatial_ref_sys.
970
51
    CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
971
972
    // Free everything that was allocated.
973
51
    sqlite3_free(pszSQL);
974
975
51
    return nSRSId;
976
51
}
977
978
/************************************************************************/
979
/*                       ~GDALGeoPackageDataset()                       */
980
/************************************************************************/
981
982
GDALGeoPackageDataset::~GDALGeoPackageDataset()
983
2.56k
{
984
2.56k
    GDALGeoPackageDataset::Close();
985
2.56k
}
986
987
/************************************************************************/
988
/*                              Close()                                 */
989
/************************************************************************/
990
991
CPLErr GDALGeoPackageDataset::Close()
992
3.01k
{
993
3.01k
    CPLErr eErr = CE_None;
994
3.01k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
995
2.56k
    {
996
2.56k
        if (eAccess == GA_Update && m_poParentDS == nullptr &&
997
2.56k
            !m_osRasterTable.empty() && !m_bGeoTransformValid)
998
0
        {
999
0
            CPLError(CE_Failure, CPLE_AppDefined,
1000
0
                     "Raster table %s not correctly initialized due to missing "
1001
0
                     "call to SetGeoTransform()",
1002
0
                     m_osRasterTable.c_str());
1003
0
        }
1004
1005
2.56k
        if (GDALGeoPackageDataset::FlushCache(true) != CE_None)
1006
0
            eErr = CE_Failure;
1007
1008
        // Destroy bands now since we don't want
1009
        // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1010
        // destruction
1011
2.60k
        for (int i = 0; i < nBands; i++)
1012
42
            delete papoBands[i];
1013
2.56k
        nBands = 0;
1014
2.56k
        CPLFree(papoBands);
1015
2.56k
        papoBands = nullptr;
1016
1017
        // Destroy overviews before cleaning m_hTempDB as they could still
1018
        // need it
1019
2.56k
        m_apoOverviewDS.clear();
1020
1021
2.56k
        if (m_poParentDS)
1022
2
        {
1023
2
            hDB = nullptr;
1024
2
        }
1025
1026
2.56k
        m_apoLayers.clear();
1027
1028
2.56k
        std::map<int, OGRSpatialReference *>::iterator oIter =
1029
2.56k
            m_oMapSrsIdToSrs.begin();
1030
2.80k
        for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1031
247
        {
1032
247
            OGRSpatialReference *poSRS = oIter->second;
1033
247
            if (poSRS)
1034
112
                poSRS->Release();
1035
247
        }
1036
1037
2.56k
        if (!CloseDB())
1038
0
            eErr = CE_Failure;
1039
1040
2.56k
        if (OGRSQLiteBaseDataSource::Close() != CE_None)
1041
0
            eErr = CE_Failure;
1042
2.56k
    }
1043
3.01k
    return eErr;
1044
3.01k
}
1045
1046
/************************************************************************/
1047
/*                         ICanIWriteBlock()                            */
1048
/************************************************************************/
1049
1050
bool GDALGeoPackageDataset::ICanIWriteBlock()
1051
0
{
1052
0
    if (!GetUpdate())
1053
0
    {
1054
0
        CPLError(
1055
0
            CE_Failure, CPLE_NotSupported,
1056
0
            "IWriteBlock() not supported on dataset opened in read-only mode");
1057
0
        return false;
1058
0
    }
1059
1060
0
    if (m_pabyCachedTiles == nullptr)
1061
0
    {
1062
0
        return false;
1063
0
    }
1064
1065
0
    if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1066
0
    {
1067
0
        CPLError(CE_Failure, CPLE_NotSupported,
1068
0
                 "IWriteBlock() not supported if georeferencing not set");
1069
0
        return false;
1070
0
    }
1071
0
    return true;
1072
0
}
1073
1074
/************************************************************************/
1075
/*                            IRasterIO()                               */
1076
/************************************************************************/
1077
1078
CPLErr GDALGeoPackageDataset::IRasterIO(
1079
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1080
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1081
    int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1082
    GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1083
1084
0
{
1085
0
    CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1086
0
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1087
0
        eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1088
0
        psExtraArg);
1089
1090
    // If writing all bands, in non-shifted mode, flush all entirely written
1091
    // tiles This can avoid "stressing" the block cache with too many dirty
1092
    // blocks. Note: this logic would be useless with a per-dataset block cache.
1093
0
    if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1094
0
        nYSize == nBufYSize && nBandCount == nBands &&
1095
0
        m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1096
0
    {
1097
0
        auto poBand =
1098
0
            cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1099
0
        int nBlockXSize, nBlockYSize;
1100
0
        poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1101
0
        const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1102
0
        const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1103
0
        const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1104
0
        const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1105
0
        for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1106
0
        {
1107
0
            for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1108
0
            {
1109
0
                GDALRasterBlock *poBlock =
1110
0
                    poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1111
0
                if (poBlock)
1112
0
                {
1113
                    // GetDirty() should be true in most situation (otherwise
1114
                    // it means the block cache is under extreme pressure!)
1115
0
                    if (poBlock->GetDirty())
1116
0
                    {
1117
                        // IWriteBlock() on one band will check the dirty state
1118
                        // of the corresponding blocks in other bands, to decide
1119
                        // if it can call WriteTile(), so we have only to do
1120
                        // that on one of the bands
1121
0
                        if (poBlock->Write() != CE_None)
1122
0
                            eErr = CE_Failure;
1123
0
                    }
1124
0
                    poBlock->DropLock();
1125
0
                }
1126
0
            }
1127
0
        }
1128
0
    }
1129
1130
0
    return eErr;
1131
0
}
1132
1133
/************************************************************************/
1134
/*                          GetOGRTableLimit()                          */
1135
/************************************************************************/
1136
1137
static int GetOGRTableLimit()
1138
998
{
1139
998
    return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1140
998
}
1141
1142
/************************************************************************/
1143
/*                      GetNameTypeMapFromSQliteMaster()                */
1144
/************************************************************************/
1145
1146
const std::map<CPLString, CPLString> &
1147
GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1148
958
{
1149
958
    if (!m_oMapNameToType.empty())
1150
776
        return m_oMapNameToType;
1151
1152
182
    CPLString osSQL(
1153
182
        "SELECT name, type FROM sqlite_master WHERE "
1154
182
        "type IN ('view', 'table') OR "
1155
182
        "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1156
182
    const int nTableLimit = GetOGRTableLimit();
1157
182
    if (nTableLimit > 0)
1158
182
    {
1159
182
        osSQL += " LIMIT ";
1160
182
        osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1161
182
    }
1162
1163
182
    auto oResult = SQLQuery(hDB, osSQL);
1164
182
    if (oResult)
1165
182
    {
1166
3.08k
        for (int i = 0; i < oResult->RowCount(); i++)
1167
2.90k
        {
1168
2.90k
            const char *pszName = oResult->GetValue(0, i);
1169
2.90k
            const char *pszType = oResult->GetValue(1, i);
1170
2.90k
            m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1171
2.90k
        }
1172
182
    }
1173
1174
182
    return m_oMapNameToType;
1175
958
}
1176
1177
/************************************************************************/
1178
/*                    RemoveTableFromSQLiteMasterCache()                */
1179
/************************************************************************/
1180
1181
void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1182
    const char *pszTableName)
1183
0
{
1184
0
    m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1185
0
}
1186
1187
/************************************************************************/
1188
/*                  GetUnknownExtensionsTableSpecific()                 */
1189
/************************************************************************/
1190
1191
const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1192
GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1193
1.24k
{
1194
1.24k
    if (m_bMapTableToExtensionsBuilt)
1195
874
        return m_oMapTableToExtensions;
1196
374
    m_bMapTableToExtensionsBuilt = true;
1197
1198
374
    if (!HasExtensionsTable())
1199
214
        return m_oMapTableToExtensions;
1200
1201
160
    CPLString osSQL(
1202
160
        "SELECT table_name, extension_name, definition, scope "
1203
160
        "FROM gpkg_extensions WHERE "
1204
160
        "table_name IS NOT NULL "
1205
160
        "AND extension_name IS NOT NULL "
1206
160
        "AND definition IS NOT NULL "
1207
160
        "AND scope IS NOT NULL "
1208
160
        "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1209
160
        "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1210
160
        "'gpkg_geom_MULTICURVE', "
1211
160
        "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1212
160
        "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1213
160
        "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1214
160
        "'gpkg_srs_id_trigger', "
1215
160
        "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1216
160
        "'gpkg_related_tables', 'related_tables'"
1217
#ifdef HAVE_SPATIALITE
1218
        ", 'gdal_spatialite_computed_geom_column'"
1219
#endif
1220
160
        ")");
1221
160
    const int nTableLimit = GetOGRTableLimit();
1222
160
    if (nTableLimit > 0)
1223
160
    {
1224
160
        osSQL += " LIMIT ";
1225
160
        osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1226
160
    }
1227
1228
160
    auto oResult = SQLQuery(hDB, osSQL);
1229
160
    if (oResult)
1230
156
    {
1231
10.6k
        for (int i = 0; i < oResult->RowCount(); i++)
1232
10.4k
        {
1233
10.4k
            const char *pszTableName = oResult->GetValue(0, i);
1234
10.4k
            const char *pszExtensionName = oResult->GetValue(1, i);
1235
10.4k
            const char *pszDefinition = oResult->GetValue(2, i);
1236
10.4k
            const char *pszScope = oResult->GetValue(3, i);
1237
10.4k
            if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1238
10.4k
            {
1239
10.4k
                GPKGExtensionDesc oDesc;
1240
10.4k
                oDesc.osExtensionName = pszExtensionName;
1241
10.4k
                oDesc.osDefinition = pszDefinition;
1242
10.4k
                oDesc.osScope = pszScope;
1243
10.4k
                m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1244
10.4k
                    .push_back(std::move(oDesc));
1245
10.4k
            }
1246
10.4k
        }
1247
156
    }
1248
1249
160
    return m_oMapTableToExtensions;
1250
374
}
1251
1252
/************************************************************************/
1253
/*                           GetContents()                              */
1254
/************************************************************************/
1255
1256
const std::map<CPLString, GPKGContentsDesc> &
1257
GDALGeoPackageDataset::GetContents()
1258
375
{
1259
375
    if (m_bMapTableToContentsBuilt)
1260
149
        return m_oMapTableToContents;
1261
226
    m_bMapTableToContentsBuilt = true;
1262
1263
226
    CPLString osSQL("SELECT table_name, data_type, identifier, "
1264
226
                    "description, min_x, min_y, max_x, max_y "
1265
226
                    "FROM gpkg_contents");
1266
226
    const int nTableLimit = GetOGRTableLimit();
1267
226
    if (nTableLimit > 0)
1268
226
    {
1269
226
        osSQL += " LIMIT ";
1270
226
        osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1271
226
    }
1272
1273
226
    auto oResult = SQLQuery(hDB, osSQL);
1274
226
    if (oResult)
1275
217
    {
1276
11.7k
        for (int i = 0; i < oResult->RowCount(); i++)
1277
11.5k
        {
1278
11.5k
            const char *pszTableName = oResult->GetValue(0, i);
1279
11.5k
            if (pszTableName == nullptr)
1280
560
                continue;
1281
10.9k
            const char *pszDataType = oResult->GetValue(1, i);
1282
10.9k
            const char *pszIdentifier = oResult->GetValue(2, i);
1283
10.9k
            const char *pszDescription = oResult->GetValue(3, i);
1284
10.9k
            const char *pszMinX = oResult->GetValue(4, i);
1285
10.9k
            const char *pszMinY = oResult->GetValue(5, i);
1286
10.9k
            const char *pszMaxX = oResult->GetValue(6, i);
1287
10.9k
            const char *pszMaxY = oResult->GetValue(7, i);
1288
10.9k
            GPKGContentsDesc oDesc;
1289
10.9k
            if (pszDataType)
1290
10.9k
                oDesc.osDataType = pszDataType;
1291
10.9k
            if (pszIdentifier)
1292
10.9k
                oDesc.osIdentifier = pszIdentifier;
1293
10.9k
            if (pszDescription)
1294
10.9k
                oDesc.osDescription = pszDescription;
1295
10.9k
            if (pszMinX)
1296
10.9k
                oDesc.osMinX = pszMinX;
1297
10.9k
            if (pszMinY)
1298
10.9k
                oDesc.osMinY = pszMinY;
1299
10.9k
            if (pszMaxX)
1300
10.7k
                oDesc.osMaxX = pszMaxX;
1301
10.9k
            if (pszMaxY)
1302
10.6k
                oDesc.osMaxY = pszMaxY;
1303
10.9k
            m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1304
10.9k
                std::move(oDesc);
1305
10.9k
        }
1306
217
    }
1307
1308
226
    return m_oMapTableToContents;
1309
375
}
1310
1311
/************************************************************************/
1312
/*                                Open()                                */
1313
/************************************************************************/
1314
1315
int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1316
                                const std::string &osFilenameInZip)
1317
2.30k
{
1318
2.30k
    m_osFilenameInZip = osFilenameInZip;
1319
2.30k
    CPLAssert(m_apoLayers.empty());
1320
2.30k
    CPLAssert(hDB == nullptr);
1321
1322
2.30k
    SetDescription(poOpenInfo->pszFilename);
1323
2.30k
    CPLString osFilename(poOpenInfo->pszFilename);
1324
2.30k
    CPLString osSubdatasetTableName;
1325
2.30k
    GByte abyHeaderLetMeHerePlease[100];
1326
2.30k
    const GByte *pabyHeader = poOpenInfo->pabyHeader;
1327
2.30k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1328
488
    {
1329
488
        char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1330
488
                                                CSLT_HONOURSTRINGS);
1331
488
        int nCount = CSLCount(papszTokens);
1332
488
        if (nCount < 2)
1333
0
        {
1334
0
            CSLDestroy(papszTokens);
1335
0
            return FALSE;
1336
0
        }
1337
1338
488
        if (nCount <= 3)
1339
209
        {
1340
209
            osFilename = papszTokens[1];
1341
209
        }
1342
        /* GPKG:C:\BLA.GPKG:foo */
1343
279
        else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1344
279
                 (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1345
0
        {
1346
0
            osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1347
0
        }
1348
        // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1349
279
        else if (/*nCount >= 4 && */
1350
279
                 (EQUAL(papszTokens[1], "/vsicurl/http") ||
1351
279
                  EQUAL(papszTokens[1], "/vsicurl/https")))
1352
206
        {
1353
206
            osFilename = CPLString(papszTokens[1]);
1354
1.29k
            for (int i = 2; i < nCount - 1; i++)
1355
1.08k
            {
1356
1.08k
                osFilename += ':';
1357
1.08k
                osFilename += papszTokens[i];
1358
1.08k
            }
1359
206
        }
1360
488
        if (nCount >= 3)
1361
377
            osSubdatasetTableName = papszTokens[nCount - 1];
1362
1363
488
        CSLDestroy(papszTokens);
1364
488
        VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1365
488
        if (fp != nullptr)
1366
211
        {
1367
211
            VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1368
211
            VSIFCloseL(fp);
1369
211
        }
1370
488
        pabyHeader = abyHeaderLetMeHerePlease;
1371
488
    }
1372
1.81k
    else if (poOpenInfo->pabyHeader &&
1373
1.81k
             STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1374
1.81k
                         "SQLite format 3"))
1375
26
    {
1376
26
        m_bCallUndeclareFileNotToOpen = true;
1377
26
        GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1378
26
                                         poOpenInfo->nHeaderBytes);
1379
26
    }
1380
1381
2.30k
    eAccess = poOpenInfo->eAccess;
1382
2.30k
    if (!m_osFilenameInZip.empty())
1383
25
    {
1384
25
        m_pszFilename = CPLStrdup(CPLSPrintf(
1385
25
            "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1386
25
    }
1387
2.27k
    else
1388
2.27k
    {
1389
2.27k
        m_pszFilename = CPLStrdup(osFilename);
1390
2.27k
    }
1391
1392
2.30k
    if (poOpenInfo->papszOpenOptions)
1393
0
    {
1394
0
        CSLDestroy(papszOpenOptions);
1395
0
        papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1396
0
    }
1397
1398
2.30k
#ifdef ENABLE_SQL_GPKG_FORMAT
1399
2.30k
    if (poOpenInfo->pabyHeader &&
1400
2.30k
        STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1401
2.30k
                    "-- SQL GPKG") &&
1402
2.30k
        poOpenInfo->fpL != nullptr)
1403
1.76k
    {
1404
1.76k
        if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1405
1.76k
            SQLITE_OK)
1406
0
        {
1407
0
            return FALSE;
1408
0
        }
1409
1410
1.76k
        InstallSQLFunctions();
1411
1412
        // Ingest the lines of the dump
1413
1.76k
        VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1414
1.76k
        const char *pszLine;
1415
2.11M
        while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1416
2.11M
        {
1417
2.11M
            if (STARTS_WITH(pszLine, "--"))
1418
4.17k
                continue;
1419
1420
2.11M
            if (!SQLCheckLineIsSafe(pszLine))
1421
0
                return false;
1422
1423
2.11M
            char *pszErrMsg = nullptr;
1424
2.11M
            if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1425
2.11M
                SQLITE_OK)
1426
1.12M
            {
1427
1.12M
                if (pszErrMsg)
1428
1.12M
                    CPLDebug("SQLITE", "Error %s", pszErrMsg);
1429
1.12M
            }
1430
2.11M
            sqlite3_free(pszErrMsg);
1431
2.11M
        }
1432
1.76k
    }
1433
1434
539
    else if (pabyHeader != nullptr)
1435
539
#endif
1436
539
    {
1437
539
        if (poOpenInfo->fpL)
1438
51
        {
1439
            // See above comment about -wal locking for the importance of
1440
            // closing that file, prior to calling sqlite3_open()
1441
51
            VSIFCloseL(poOpenInfo->fpL);
1442
51
            poOpenInfo->fpL = nullptr;
1443
51
        }
1444
1445
        /* See if we can open the SQLite database */
1446
539
        if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1447
539
                                        : SQLITE_OPEN_READONLY))
1448
286
            return FALSE;
1449
1450
253
        memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1451
253
        m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1452
253
        memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1453
253
        m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1454
253
        if (m_nApplicationId == GP10_APPLICATION_ID)
1455
1
        {
1456
1
            CPLDebug("GPKG", "GeoPackage v1.0");
1457
1
        }
1458
252
        else if (m_nApplicationId == GP11_APPLICATION_ID)
1459
0
        {
1460
0
            CPLDebug("GPKG", "GeoPackage v1.1");
1461
0
        }
1462
252
        else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1463
252
                 m_nUserVersion >= GPKG_1_2_VERSION)
1464
23
        {
1465
23
            CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1466
23
                     (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1467
23
        }
1468
253
    }
1469
1470
    /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1471
     * “ok” */
1472
    /* http://opengis.github.io/geopackage/#_file_integrity */
1473
    /* Disable integrity check by default, since it is expensive on big files */
1474
2.01k
    if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1475
2.01k
        OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1476
0
    {
1477
0
        CPLError(CE_Failure, CPLE_AppDefined,
1478
0
                 "pragma integrity_check on '%s' failed", m_pszFilename);
1479
0
        return FALSE;
1480
0
    }
1481
1482
    /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1483
    /* parameter value SHALL return an empty result set */
1484
    /* http://opengis.github.io/geopackage/#_file_integrity */
1485
    /* Disable the check by default, since it is to corrupt databases, and */
1486
    /* that causes issues to downstream software that can't open them. */
1487
2.01k
    if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1488
2.01k
        OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1489
0
    {
1490
0
        CPLError(CE_Failure, CPLE_AppDefined,
1491
0
                 "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1492
0
        return FALSE;
1493
0
    }
1494
1495
    /* Check for requirement metadata tables */
1496
    /* Requirement 10: gpkg_spatial_ref_sys must exist */
1497
    /* Requirement 13: gpkg_contents must exist */
1498
2.01k
    if (SQLGetInteger(hDB,
1499
2.01k
                      "SELECT COUNT(*) FROM sqlite_master WHERE "
1500
2.01k
                      "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1501
2.01k
                      "type IN ('table', 'view')",
1502
2.01k
                      nullptr) != 2)
1503
1.58k
    {
1504
1.58k
        CPLError(CE_Failure, CPLE_AppDefined,
1505
1.58k
                 "At least one of the required GeoPackage tables, "
1506
1.58k
                 "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1507
1.58k
        return FALSE;
1508
1.58k
    }
1509
1510
435
    DetectSpatialRefSysColumns();
1511
1512
435
#ifdef ENABLE_GPKG_OGR_CONTENTS
1513
435
    if (SQLGetInteger(hDB,
1514
435
                      "SELECT 1 FROM sqlite_master WHERE "
1515
435
                      "name = 'gpkg_ogr_contents' AND type = 'table'",
1516
435
                      nullptr) == 1)
1517
186
    {
1518
186
        m_bHasGPKGOGRContents = true;
1519
186
    }
1520
435
#endif
1521
1522
435
    CheckUnknownExtensions();
1523
1524
435
    int bRet = FALSE;
1525
435
    bool bHasGPKGExtRelations = false;
1526
435
    if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1527
419
    {
1528
419
        m_bHasGPKGGeometryColumns =
1529
419
            SQLGetInteger(hDB,
1530
419
                          "SELECT 1 FROM sqlite_master WHERE "
1531
419
                          "name = 'gpkg_geometry_columns' AND "
1532
419
                          "type IN ('table', 'view')",
1533
419
                          nullptr) == 1;
1534
419
        bHasGPKGExtRelations = HasGpkgextRelationsTable();
1535
419
    }
1536
435
    if (m_bHasGPKGGeometryColumns)
1537
414
    {
1538
        /* Load layer definitions for all tables in gpkg_contents &
1539
         * gpkg_geometry_columns */
1540
        /* and non-spatial tables as well */
1541
414
        std::string osSQL =
1542
414
            "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1543
414
            "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1544
414
            "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1545
414
            "(SELECT type FROM sqlite_master WHERE lower(name) = "
1546
414
            "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1547
414
            "  FROM gpkg_geometry_columns g "
1548
414
            "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1549
414
            "  WHERE "
1550
414
            "  c.table_name <> 'ogr_empty_table' AND"
1551
414
            "  c.data_type = 'features' "
1552
            // aspatial: Was the only method available in OGR 2.0 and 2.1
1553
            // attributes: GPKG 1.2 or later
1554
414
            "UNION ALL "
1555
414
            "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1556
414
            "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1557
414
            "is_in_gpkg_contents, "
1558
414
            "(SELECT type FROM sqlite_master WHERE lower(name) = "
1559
414
            "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1560
414
            "  FROM gpkg_contents"
1561
414
            "  WHERE data_type IN ('aspatial', 'attributes') ";
1562
1563
414
        const char *pszListAllTables = CSLFetchNameValueDef(
1564
414
            poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1565
414
        bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1566
414
        if (!bHasASpatialOrAttributes)
1567
414
        {
1568
414
            auto oResultTable =
1569
414
                SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1570
414
                              "data_type = 'attributes' LIMIT 1");
1571
414
            bHasASpatialOrAttributes =
1572
414
                (oResultTable && oResultTable->RowCount() == 1);
1573
414
        }
1574
414
        if (bHasGPKGExtRelations)
1575
1
        {
1576
1
            osSQL += "UNION ALL "
1577
1
                     "SELECT mapping_table_name, mapping_table_name, 0 as "
1578
1
                     "is_spatial, NULL, NULL, 0, 0, 0 AS "
1579
1
                     "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1580
1
                     "is_in_gpkg_contents, 'table' AS object_type "
1581
1
                     "FROM gpkgext_relations WHERE "
1582
1
                     "lower(mapping_table_name) NOT IN (SELECT "
1583
1
                     "lower(table_name) FROM gpkg_contents) AND "
1584
1
                     "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1585
1
                     "type IN ('table', 'view') AND "
1586
1
                     "lower(name) = lower(mapping_table_name))";
1587
1
        }
1588
414
        if (EQUAL(pszListAllTables, "YES") ||
1589
414
            (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1590
384
        {
1591
            // vgpkg_ is Spatialite virtual table
1592
384
            osSQL +=
1593
384
                "UNION ALL "
1594
384
                "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1595
384
                "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1596
384
                "is_in_gpkg_contents, type AS object_type "
1597
384
                "FROM sqlite_master WHERE type IN ('table', 'view') "
1598
384
                "AND name NOT LIKE 'gpkg_%' "
1599
384
                "AND name NOT LIKE 'vgpkg_%' "
1600
384
                "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1601
                // Avoid reading those views from simple_sewer_features.gpkg
1602
384
                "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1603
384
                "'st_geometry_columns', 'geometry_columns') "
1604
384
                "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1605
384
                "gpkg_contents)";
1606
384
            if (bHasGPKGExtRelations)
1607
1
            {
1608
1
                osSQL += " AND lower(name) NOT IN (SELECT "
1609
1
                         "lower(mapping_table_name) FROM "
1610
1
                         "gpkgext_relations)";
1611
1
            }
1612
384
        }
1613
414
        const int nTableLimit = GetOGRTableLimit();
1614
414
        if (nTableLimit > 0)
1615
414
        {
1616
414
            osSQL += " LIMIT ";
1617
414
            osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1618
414
        }
1619
1620
414
        auto oResult = SQLQuery(hDB, osSQL.c_str());
1621
414
        if (!oResult)
1622
1
        {
1623
1
            return FALSE;
1624
1
        }
1625
1626
413
        if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1627
9
        {
1628
9
            CPLError(CE_Warning, CPLE_AppDefined,
1629
9
                     "File has more than %d vector tables. "
1630
9
                     "Limiting to first %d (can be overridden with "
1631
9
                     "OGR_TABLE_LIMIT config option)",
1632
9
                     nTableLimit, nTableLimit);
1633
9
            oResult->LimitRowCount(nTableLimit);
1634
9
        }
1635
1636
413
        if (oResult->RowCount() > 0)
1637
410
        {
1638
410
            bRet = TRUE;
1639
1640
410
            m_apoLayers.reserve(oResult->RowCount());
1641
1642
410
            std::map<std::string, int> oMapTableRefCount;
1643
165k
            for (int i = 0; i < oResult->RowCount(); i++)
1644
164k
            {
1645
164k
                const char *pszTableName = oResult->GetValue(0, i);
1646
164k
                if (pszTableName == nullptr)
1647
0
                    continue;
1648
164k
                if (++oMapTableRefCount[pszTableName] == 2)
1649
199
                {
1650
                    // This should normally not happen if all constraints are
1651
                    // properly set
1652
199
                    CPLError(CE_Warning, CPLE_AppDefined,
1653
199
                             "Table %s appearing several times in "
1654
199
                             "gpkg_contents and/or gpkg_geometry_columns",
1655
199
                             pszTableName);
1656
199
                }
1657
164k
            }
1658
1659
410
            std::set<std::string> oExistingLayers;
1660
165k
            for (int i = 0; i < oResult->RowCount(); i++)
1661
164k
            {
1662
164k
                const char *pszTableName = oResult->GetValue(0, i);
1663
164k
                if (pszTableName == nullptr)
1664
0
                    continue;
1665
164k
                const bool bTableHasSeveralGeomColumns =
1666
164k
                    oMapTableRefCount[pszTableName] > 1;
1667
164k
                bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1668
164k
                const char *pszGeomColName = oResult->GetValue(3, i);
1669
164k
                const char *pszGeomType = oResult->GetValue(4, i);
1670
164k
                const char *pszZ = oResult->GetValue(5, i);
1671
164k
                const char *pszM = oResult->GetValue(6, i);
1672
164k
                bool bIsInGpkgContents =
1673
164k
                    CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1674
164k
                if (!bIsInGpkgContents)
1675
961
                    m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1676
164k
                const char *pszObjectType = oResult->GetValue(12, i);
1677
164k
                if (pszObjectType == nullptr ||
1678
164k
                    !(EQUAL(pszObjectType, "table") ||
1679
163k
                      EQUAL(pszObjectType, "view")))
1680
1.07k
                {
1681
1.07k
                    CPLError(CE_Warning, CPLE_AppDefined,
1682
1.07k
                             "Table/view %s is referenced in gpkg_contents, "
1683
1.07k
                             "but does not exist",
1684
1.07k
                             pszTableName);
1685
1.07k
                    continue;
1686
1.07k
                }
1687
                // Non-standard and undocumented behavior:
1688
                // if the same table appears to have several geometry columns,
1689
                // handle it for now as multiple layers named
1690
                // "table_name (geom_col_name)"
1691
                // The way we handle that might change in the future (e.g
1692
                // could be a single layer with multiple geometry columns)
1693
163k
                std::string osLayerNameWithGeomColName =
1694
163k
                    pszGeomColName ? std::string(pszTableName) + " (" +
1695
162k
                                         pszGeomColName + ')'
1696
163k
                                   : std::string(pszTableName);
1697
163k
                if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1698
161k
                    continue;
1699
2.20k
                oExistingLayers.insert(osLayerNameWithGeomColName);
1700
2.20k
                const std::string osLayerName =
1701
2.20k
                    bTableHasSeveralGeomColumns
1702
2.20k
                        ? std::move(osLayerNameWithGeomColName)
1703
2.20k
                        : std::string(pszTableName);
1704
2.20k
                auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1705
2.20k
                    this, osLayerName.c_str());
1706
2.20k
                bool bHasZ = pszZ && atoi(pszZ) > 0;
1707
2.20k
                bool bHasM = pszM && atoi(pszM) > 0;
1708
2.20k
                if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1709
411
                {
1710
411
                    if (pszZ && atoi(pszZ) == 2)
1711
3
                        bHasZ = false;
1712
411
                    if (pszM && atoi(pszM) == 2)
1713
3
                        bHasM = false;
1714
411
                }
1715
2.20k
                poLayer->SetOpeningParameters(
1716
2.20k
                    pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1717
2.20k
                    pszGeomColName, pszGeomType, bHasZ, bHasM);
1718
2.20k
                m_apoLayers.push_back(std::move(poLayer));
1719
2.20k
            }
1720
410
        }
1721
413
    }
1722
1723
434
    bool bHasTileMatrixSet = false;
1724
434
    if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1725
16
    {
1726
16
        bHasTileMatrixSet = SQLGetInteger(hDB,
1727
16
                                          "SELECT 1 FROM sqlite_master WHERE "
1728
16
                                          "name = 'gpkg_tile_matrix_set' AND "
1729
16
                                          "type IN ('table', 'view')",
1730
16
                                          nullptr) == 1;
1731
16
    }
1732
434
    if (bHasTileMatrixSet)
1733
16
    {
1734
16
        std::string osSQL =
1735
16
            "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1736
16
            "c.min_x, c.min_y, c.max_x, c.max_y, "
1737
16
            "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1738
16
            "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1739
16
            "c.table_name = tms.table_name WHERE "
1740
16
            "data_type IN ('tiles', '2d-gridded-coverage')";
1741
16
        if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1742
0
            osSubdatasetTableName =
1743
0
                CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1744
16
        if (!osSubdatasetTableName.empty())
1745
0
        {
1746
0
            char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1747
0
                                           osSubdatasetTableName.c_str());
1748
0
            osSQL += pszTmp;
1749
0
            sqlite3_free(pszTmp);
1750
0
            SetPhysicalFilename(osFilename.c_str());
1751
0
        }
1752
16
        const int nTableLimit = GetOGRTableLimit();
1753
16
        if (nTableLimit > 0)
1754
16
        {
1755
16
            osSQL += " LIMIT ";
1756
16
            osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1757
16
        }
1758
1759
16
        auto oResult = SQLQuery(hDB, osSQL.c_str());
1760
16
        if (!oResult)
1761
1
        {
1762
1
            return FALSE;
1763
1
        }
1764
1765
15
        if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1766
0
        {
1767
0
            CPLError(CE_Failure, CPLE_AppDefined,
1768
0
                     "Cannot find table '%s' in GeoPackage dataset",
1769
0
                     osSubdatasetTableName.c_str());
1770
0
        }
1771
15
        else if (oResult->RowCount() == 1)
1772
10
        {
1773
10
            const char *pszTableName = oResult->GetValue(0, 0);
1774
10
            const char *pszIdentifier = oResult->GetValue(1, 0);
1775
10
            const char *pszDescription = oResult->GetValue(2, 0);
1776
10
            const char *pszSRSId = oResult->GetValue(3, 0);
1777
10
            const char *pszMinX = oResult->GetValue(4, 0);
1778
10
            const char *pszMinY = oResult->GetValue(5, 0);
1779
10
            const char *pszMaxX = oResult->GetValue(6, 0);
1780
10
            const char *pszMaxY = oResult->GetValue(7, 0);
1781
10
            const char *pszTMSMinX = oResult->GetValue(8, 0);
1782
10
            const char *pszTMSMinY = oResult->GetValue(9, 0);
1783
10
            const char *pszTMSMaxX = oResult->GetValue(10, 0);
1784
10
            const char *pszTMSMaxY = oResult->GetValue(11, 0);
1785
10
            const char *pszDataType = oResult->GetValue(12, 0);
1786
10
            if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1787
10
                pszTMSMaxY)
1788
10
            {
1789
10
                bRet = OpenRaster(
1790
10
                    pszTableName, pszIdentifier, pszDescription,
1791
10
                    pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1792
10
                    CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1793
10
                    CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1794
10
                    EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1795
10
            }
1796
10
        }
1797
5
        else if (oResult->RowCount() >= 1)
1798
3
        {
1799
3
            bRet = TRUE;
1800
1801
3
            if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1802
0
            {
1803
0
                CPLError(CE_Warning, CPLE_AppDefined,
1804
0
                         "File has more than %d raster tables. "
1805
0
                         "Limiting to first %d (can be overridden with "
1806
0
                         "OGR_TABLE_LIMIT config option)",
1807
0
                         nTableLimit, nTableLimit);
1808
0
                oResult->LimitRowCount(nTableLimit);
1809
0
            }
1810
1811
3
            int nSDSCount = 0;
1812
37
            for (int i = 0; i < oResult->RowCount(); i++)
1813
34
            {
1814
34
                const char *pszTableName = oResult->GetValue(0, i);
1815
34
                const char *pszIdentifier = oResult->GetValue(1, i);
1816
34
                if (pszTableName == nullptr)
1817
0
                    continue;
1818
34
                m_aosSubDatasets.AddNameValue(
1819
34
                    CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1820
34
                    CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1821
34
                m_aosSubDatasets.AddNameValue(
1822
34
                    CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1823
34
                    pszIdentifier
1824
34
                        ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1825
34
                        : pszTableName);
1826
34
                nSDSCount++;
1827
34
            }
1828
3
        }
1829
15
    }
1830
1831
433
    if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1832
8
    {
1833
8
        if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1834
0
        {
1835
0
            bRet = TRUE;
1836
0
        }
1837
8
        else
1838
8
        {
1839
8
            CPLDebug("GPKG",
1840
8
                     "This GeoPackage has no vector content and is opened "
1841
8
                     "in read-only mode. If you open it in update mode, "
1842
8
                     "opening will be successful.");
1843
8
        }
1844
8
    }
1845
1846
433
    if (eAccess == GA_Update)
1847
0
    {
1848
0
        FixupWrongRTreeTrigger();
1849
0
        FixupWrongMedataReferenceColumnNameUpdate();
1850
0
    }
1851
1852
433
    SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1853
1854
433
    return bRet;
1855
434
}
1856
1857
/************************************************************************/
1858
/*                    DetectSpatialRefSysColumns()                      */
1859
/************************************************************************/
1860
1861
void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1862
435
{
1863
    // Detect definition_12_063 column
1864
435
    {
1865
435
        sqlite3_stmt *hSQLStmt = nullptr;
1866
435
        int rc = sqlite3_prepare_v2(
1867
435
            hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1868
435
            &hSQLStmt, nullptr);
1869
435
        if (rc == SQLITE_OK)
1870
0
        {
1871
0
            m_bHasDefinition12_063 = true;
1872
0
            sqlite3_finalize(hSQLStmt);
1873
0
        }
1874
435
    }
1875
1876
    // Detect epoch column
1877
435
    if (m_bHasDefinition12_063)
1878
0
    {
1879
0
        sqlite3_stmt *hSQLStmt = nullptr;
1880
0
        int rc =
1881
0
            sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1882
0
                               -1, &hSQLStmt, nullptr);
1883
0
        if (rc == SQLITE_OK)
1884
0
        {
1885
0
            m_bHasEpochColumn = true;
1886
0
            sqlite3_finalize(hSQLStmt);
1887
0
        }
1888
0
    }
1889
435
}
1890
1891
/************************************************************************/
1892
/*                    FixupWrongRTreeTrigger()                          */
1893
/************************************************************************/
1894
1895
void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1896
0
{
1897
0
    auto oResult = SQLQuery(
1898
0
        hDB,
1899
0
        "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1900
0
        "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1901
0
    if (oResult == nullptr)
1902
0
        return;
1903
0
    if (oResult->RowCount() > 0)
1904
0
    {
1905
0
        CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1906
0
    }
1907
0
    for (int i = 0; i < oResult->RowCount(); i++)
1908
0
    {
1909
0
        const char *pszName = oResult->GetValue(0, i);
1910
0
        const char *pszSQL = oResult->GetValue(1, i);
1911
0
        const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1912
0
        if (pszPtr1)
1913
0
        {
1914
0
            const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1915
            // Skipping over geometry column name
1916
0
            while (*pszPtr == ' ')
1917
0
                pszPtr++;
1918
0
            if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1919
0
            {
1920
0
                char chStringDelim = pszPtr[0];
1921
0
                pszPtr++;
1922
0
                while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1923
0
                {
1924
0
                    if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1925
0
                        pszPtr += 2;
1926
0
                    else
1927
0
                        pszPtr += 1;
1928
0
                }
1929
0
                if (*pszPtr == chStringDelim)
1930
0
                    pszPtr++;
1931
0
            }
1932
0
            else
1933
0
            {
1934
0
                pszPtr++;
1935
0
                while (*pszPtr != ' ')
1936
0
                    pszPtr++;
1937
0
            }
1938
0
            if (*pszPtr == ' ')
1939
0
            {
1940
0
                SQLCommand(hDB,
1941
0
                           ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1942
0
                               .c_str());
1943
0
                CPLString newSQL;
1944
0
                newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1945
0
                newSQL += " AFTER UPDATE";
1946
0
                newSQL += pszPtr;
1947
0
                SQLCommand(hDB, newSQL);
1948
0
            }
1949
0
        }
1950
0
    }
1951
0
}
1952
1953
/************************************************************************/
1954
/*             FixupWrongMedataReferenceColumnNameUpdate()              */
1955
/************************************************************************/
1956
1957
void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1958
0
{
1959
    // Fix wrong trigger that was generated by GDAL < 2.4.0
1960
    // See https://github.com/qgis/QGIS/issues/42768
1961
0
    auto oResult = SQLQuery(
1962
0
        hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1963
0
             "NAME ='gpkg_metadata_reference_column_name_update' AND "
1964
0
             "sql LIKE '%column_nameIS%'");
1965
0
    if (oResult == nullptr)
1966
0
        return;
1967
0
    if (oResult->RowCount() == 1)
1968
0
    {
1969
0
        CPLDebug("GPKG", "Fixing incorrect trigger "
1970
0
                         "gpkg_metadata_reference_column_name_update");
1971
0
        const char *pszSQL = oResult->GetValue(0, 0);
1972
0
        std::string osNewSQL(
1973
0
            CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1974
1975
0
        SQLCommand(hDB,
1976
0
                   "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1977
0
        SQLCommand(hDB, osNewSQL.c_str());
1978
0
    }
1979
0
}
1980
1981
/************************************************************************/
1982
/*                  ClearCachedRelationships()                          */
1983
/************************************************************************/
1984
1985
void GDALGeoPackageDataset::ClearCachedRelationships()
1986
0
{
1987
0
    m_bHasPopulatedRelationships = false;
1988
0
    m_osMapRelationships.clear();
1989
0
}
1990
1991
/************************************************************************/
1992
/*                           LoadRelationships()                        */
1993
/************************************************************************/
1994
1995
void GDALGeoPackageDataset::LoadRelationships() const
1996
0
{
1997
0
    m_osMapRelationships.clear();
1998
1999
0
    std::vector<std::string> oExcludedTables;
2000
0
    if (HasGpkgextRelationsTable())
2001
0
    {
2002
0
        LoadRelationshipsUsingRelatedTablesExtension();
2003
2004
0
        for (const auto &oRelationship : m_osMapRelationships)
2005
0
        {
2006
0
            oExcludedTables.emplace_back(
2007
0
                oRelationship.second->GetMappingTableName());
2008
0
        }
2009
0
    }
2010
2011
    // Also load relationships defined using foreign keys (i.e. one-to-many
2012
    // relationships). Here we must exclude any relationships defined from the
2013
    // related tables extension, we don't want them included twice.
2014
0
    LoadRelationshipsFromForeignKeys(oExcludedTables);
2015
0
    m_bHasPopulatedRelationships = true;
2016
0
}
2017
2018
/************************************************************************/
2019
/*         LoadRelationshipsUsingRelatedTablesExtension()               */
2020
/************************************************************************/
2021
2022
void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2023
0
{
2024
0
    m_osMapRelationships.clear();
2025
2026
0
    auto oResultTable = SQLQuery(
2027
0
        hDB, "SELECT base_table_name, base_primary_column, "
2028
0
             "related_table_name, related_primary_column, relation_name, "
2029
0
             "mapping_table_name FROM gpkgext_relations");
2030
0
    if (oResultTable && oResultTable->RowCount() > 0)
2031
0
    {
2032
0
        for (int i = 0; i < oResultTable->RowCount(); i++)
2033
0
        {
2034
0
            const char *pszBaseTableName = oResultTable->GetValue(0, i);
2035
0
            if (!pszBaseTableName)
2036
0
            {
2037
0
                CPLError(CE_Warning, CPLE_AppDefined,
2038
0
                         "Could not retrieve base_table_name from "
2039
0
                         "gpkgext_relations");
2040
0
                continue;
2041
0
            }
2042
0
            const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2043
0
            if (!pszBasePrimaryColumn)
2044
0
            {
2045
0
                CPLError(CE_Warning, CPLE_AppDefined,
2046
0
                         "Could not retrieve base_primary_column from "
2047
0
                         "gpkgext_relations");
2048
0
                continue;
2049
0
            }
2050
0
            const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2051
0
            if (!pszRelatedTableName)
2052
0
            {
2053
0
                CPLError(CE_Warning, CPLE_AppDefined,
2054
0
                         "Could not retrieve related_table_name from "
2055
0
                         "gpkgext_relations");
2056
0
                continue;
2057
0
            }
2058
0
            const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2059
0
            if (!pszRelatedPrimaryColumn)
2060
0
            {
2061
0
                CPLError(CE_Warning, CPLE_AppDefined,
2062
0
                         "Could not retrieve related_primary_column from "
2063
0
                         "gpkgext_relations");
2064
0
                continue;
2065
0
            }
2066
0
            const char *pszRelationName = oResultTable->GetValue(4, i);
2067
0
            if (!pszRelationName)
2068
0
            {
2069
0
                CPLError(
2070
0
                    CE_Warning, CPLE_AppDefined,
2071
0
                    "Could not retrieve relation_name from gpkgext_relations");
2072
0
                continue;
2073
0
            }
2074
0
            const char *pszMappingTableName = oResultTable->GetValue(5, i);
2075
0
            if (!pszMappingTableName)
2076
0
            {
2077
0
                CPLError(CE_Warning, CPLE_AppDefined,
2078
0
                         "Could not retrieve mapping_table_name from "
2079
0
                         "gpkgext_relations");
2080
0
                continue;
2081
0
            }
2082
2083
            // confirm that mapping table exists
2084
0
            char *pszSQL =
2085
0
                sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2086
0
                                "name='%q' AND type IN ('table', 'view')",
2087
0
                                pszMappingTableName);
2088
0
            const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2089
0
            sqlite3_free(pszSQL);
2090
2091
0
            if (nMappingTableCount < 1 &&
2092
0
                !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2093
0
                    pszMappingTableName))
2094
0
            {
2095
0
                CPLError(CE_Warning, CPLE_AppDefined,
2096
0
                         "Relationship mapping table %s does not exist",
2097
0
                         pszMappingTableName);
2098
0
                continue;
2099
0
            }
2100
2101
0
            const std::string osRelationName = GenerateNameForRelationship(
2102
0
                pszBaseTableName, pszRelatedTableName, pszRelationName);
2103
2104
0
            std::string osType{};
2105
            // defined requirement classes -- for these types the relation name
2106
            // will be specific string value from the related tables extension.
2107
            // In this case we need to construct a unique relationship name
2108
            // based on the related tables
2109
0
            if (EQUAL(pszRelationName, "media") ||
2110
0
                EQUAL(pszRelationName, "simple_attributes") ||
2111
0
                EQUAL(pszRelationName, "features") ||
2112
0
                EQUAL(pszRelationName, "attributes") ||
2113
0
                EQUAL(pszRelationName, "tiles"))
2114
0
            {
2115
0
                osType = pszRelationName;
2116
0
            }
2117
0
            else
2118
0
            {
2119
                // user defined types default to features
2120
0
                osType = "features";
2121
0
            }
2122
2123
0
            auto poRelationship = std::make_unique<GDALRelationship>(
2124
0
                osRelationName, pszBaseTableName, pszRelatedTableName,
2125
0
                GRC_MANY_TO_MANY);
2126
2127
0
            poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2128
0
            poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2129
0
            poRelationship->SetLeftMappingTableFields({"base_id"});
2130
0
            poRelationship->SetRightMappingTableFields({"related_id"});
2131
0
            poRelationship->SetMappingTableName(pszMappingTableName);
2132
0
            poRelationship->SetRelatedTableType(osType);
2133
2134
0
            m_osMapRelationships[osRelationName] = std::move(poRelationship);
2135
0
        }
2136
0
    }
2137
0
}
2138
2139
/************************************************************************/
2140
/*                GenerateNameForRelationship()                         */
2141
/************************************************************************/
2142
2143
std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2144
    const char *pszBaseTableName, const char *pszRelatedTableName,
2145
    const char *pszType)
2146
0
{
2147
    // defined requirement classes -- for these types the relation name will be
2148
    // specific string value from the related tables extension. In this case we
2149
    // need to construct a unique relationship name based on the related tables
2150
0
    if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2151
0
        EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2152
0
        EQUAL(pszType, "tiles"))
2153
0
    {
2154
0
        std::ostringstream stream;
2155
0
        stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2156
0
               << pszType;
2157
0
        return stream.str();
2158
0
    }
2159
0
    else
2160
0
    {
2161
        // user defined types default to features
2162
0
        return pszType;
2163
0
    }
2164
0
}
2165
2166
/************************************************************************/
2167
/*                       ValidateRelationship()                         */
2168
/************************************************************************/
2169
2170
bool GDALGeoPackageDataset::ValidateRelationship(
2171
    const GDALRelationship *poRelationship, std::string &failureReason)
2172
0
{
2173
2174
0
    if (poRelationship->GetCardinality() !=
2175
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2176
0
    {
2177
0
        failureReason = "Only many to many relationships are supported";
2178
0
        return false;
2179
0
    }
2180
2181
0
    std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2182
0
    if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2183
0
        osRelatedTableType != "media" &&
2184
0
        osRelatedTableType != "simple_attributes" &&
2185
0
        osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2186
0
    {
2187
0
        failureReason =
2188
0
            ("Related table type " + osRelatedTableType +
2189
0
             " is not a valid value for the GeoPackage specification. "
2190
0
             "Valid values are: features, media, simple_attributes, "
2191
0
             "attributes, tiles.")
2192
0
                .c_str();
2193
0
        return false;
2194
0
    }
2195
2196
0
    const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2197
0
    OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2198
0
        GetLayerByName(osLeftTableName.c_str()));
2199
0
    if (!poLeftTable)
2200
0
    {
2201
0
        failureReason = ("Left table " + osLeftTableName +
2202
0
                         " is not an existing layer in the dataset")
2203
0
                            .c_str();
2204
0
        return false;
2205
0
    }
2206
0
    const std::string &osRightTableName = poRelationship->GetRightTableName();
2207
0
    OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2208
0
        GetLayerByName(osRightTableName.c_str()));
2209
0
    if (!poRightTable)
2210
0
    {
2211
0
        failureReason = ("Right table " + osRightTableName +
2212
0
                         " is not an existing layer in the dataset")
2213
0
                            .c_str();
2214
0
        return false;
2215
0
    }
2216
2217
0
    const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2218
0
    if (aosLeftTableFields.empty())
2219
0
    {
2220
0
        failureReason = "No left table fields were specified";
2221
0
        return false;
2222
0
    }
2223
0
    else if (aosLeftTableFields.size() > 1)
2224
0
    {
2225
0
        failureReason = "Only a single left table field is permitted for the "
2226
0
                        "GeoPackage specification";
2227
0
        return false;
2228
0
    }
2229
0
    else
2230
0
    {
2231
        // validate left field exists
2232
0
        if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2233
0
                aosLeftTableFields[0].c_str()) < 0 &&
2234
0
            !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2235
0
        {
2236
0
            failureReason = ("Left table field " + aosLeftTableFields[0] +
2237
0
                             " does not exist in " + osLeftTableName)
2238
0
                                .c_str();
2239
0
            return false;
2240
0
        }
2241
0
    }
2242
2243
0
    const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2244
0
    if (aosRightTableFields.empty())
2245
0
    {
2246
0
        failureReason = "No right table fields were specified";
2247
0
        return false;
2248
0
    }
2249
0
    else if (aosRightTableFields.size() > 1)
2250
0
    {
2251
0
        failureReason = "Only a single right table field is permitted for the "
2252
0
                        "GeoPackage specification";
2253
0
        return false;
2254
0
    }
2255
0
    else
2256
0
    {
2257
        // validate right field exists
2258
0
        if (poRightTable->GetLayerDefn()->GetFieldIndex(
2259
0
                aosRightTableFields[0].c_str()) < 0 &&
2260
0
            !EQUAL(poRightTable->GetFIDColumn(),
2261
0
                   aosRightTableFields[0].c_str()))
2262
0
        {
2263
0
            failureReason = ("Right table field " + aosRightTableFields[0] +
2264
0
                             " does not exist in " + osRightTableName)
2265
0
                                .c_str();
2266
0
            return false;
2267
0
        }
2268
0
    }
2269
2270
0
    return true;
2271
0
}
2272
2273
/************************************************************************/
2274
/*                         InitRaster()                                 */
2275
/************************************************************************/
2276
2277
bool GDALGeoPackageDataset::InitRaster(
2278
    GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2279
    double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2280
    const char *pszContentsMinY, const char *pszContentsMaxX,
2281
    const char *pszContentsMaxY, char **papszOpenOptionsIn,
2282
    const SQLResult &oResult, int nIdxInResult)
2283
12
{
2284
12
    m_osRasterTable = pszTableName;
2285
12
    m_dfTMSMinX = dfMinX;
2286
12
    m_dfTMSMaxY = dfMaxY;
2287
2288
    // Despite prior checking, the type might be Binary and
2289
    // SQLResultGetValue() not working properly on it
2290
12
    int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2291
12
    if (nZoomLevel < 0 || nZoomLevel > 65536)
2292
0
    {
2293
0
        return false;
2294
0
    }
2295
12
    double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2296
12
    double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2297
12
    if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2298
0
    {
2299
0
        return false;
2300
0
    }
2301
12
    int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2302
12
    int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2303
12
    if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2304
12
        nTileHeight > 65536)
2305
0
    {
2306
0
        return false;
2307
0
    }
2308
12
    int nTileMatrixWidth = static_cast<int>(
2309
12
        std::min(static_cast<GIntBig>(INT_MAX),
2310
12
                 CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2311
12
    int nTileMatrixHeight = static_cast<int>(
2312
12
        std::min(static_cast<GIntBig>(INT_MAX),
2313
12
                 CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2314
12
    if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2315
0
    {
2316
0
        return false;
2317
0
    }
2318
2319
    /* Use content bounds in priority over tile_matrix_set bounds */
2320
12
    double dfGDALMinX = dfMinX;
2321
12
    double dfGDALMinY = dfMinY;
2322
12
    double dfGDALMaxX = dfMaxX;
2323
12
    double dfGDALMaxY = dfMaxY;
2324
12
    pszContentsMinX =
2325
12
        CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2326
12
    pszContentsMinY =
2327
12
        CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2328
12
    pszContentsMaxX =
2329
12
        CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2330
12
    pszContentsMaxY =
2331
12
        CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2332
12
    if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2333
12
        pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2334
12
    {
2335
12
        if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2336
12
            CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2337
12
        {
2338
12
            dfGDALMinX = CPLAtof(pszContentsMinX);
2339
12
            dfGDALMinY = CPLAtof(pszContentsMinY);
2340
12
            dfGDALMaxX = CPLAtof(pszContentsMaxX);
2341
12
            dfGDALMaxY = CPLAtof(pszContentsMaxY);
2342
12
        }
2343
0
        else
2344
0
        {
2345
0
            CPLError(CE_Warning, CPLE_AppDefined,
2346
0
                     "Illegal min_x/min_y/max_x/max_y values for %s in open "
2347
0
                     "options and/or gpkg_contents. Using bounds of "
2348
0
                     "gpkg_tile_matrix_set instead",
2349
0
                     pszTableName);
2350
0
        }
2351
12
    }
2352
12
    if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2353
0
    {
2354
0
        CPLError(CE_Failure, CPLE_AppDefined,
2355
0
                 "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2356
0
        return false;
2357
0
    }
2358
2359
12
    int nBandCount = 0;
2360
12
    const char *pszBAND_COUNT =
2361
12
        CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2362
12
    if (poParentDS)
2363
2
    {
2364
2
        nBandCount = poParentDS->GetRasterCount();
2365
2
    }
2366
10
    else if (m_eDT != GDT_Byte)
2367
2
    {
2368
2
        if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2369
2
            !EQUAL(pszBAND_COUNT, "1"))
2370
0
        {
2371
0
            CPLError(CE_Warning, CPLE_AppDefined,
2372
0
                     "BAND_COUNT ignored for non-Byte data");
2373
0
        }
2374
2
        nBandCount = 1;
2375
2
    }
2376
8
    else
2377
8
    {
2378
8
        if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2379
0
        {
2380
0
            nBandCount = atoi(pszBAND_COUNT);
2381
0
            if (nBandCount == 1)
2382
0
                GetMetadata("IMAGE_STRUCTURE");
2383
0
        }
2384
8
        else
2385
8
        {
2386
8
            GetMetadata("IMAGE_STRUCTURE");
2387
8
            nBandCount = m_nBandCountFromMetadata;
2388
8
            if (nBandCount == 1)
2389
0
                m_eTF = GPKG_TF_PNG;
2390
8
        }
2391
8
        if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2392
0
        {
2393
0
            m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2394
0
        }
2395
8
        if (nBandCount <= 0 || nBandCount > 4)
2396
8
            nBandCount = 4;
2397
8
    }
2398
2399
12
    return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2400
12
                      dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2401
12
                      nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2402
12
                      dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2403
12
}
2404
2405
/************************************************************************/
2406
/*                      ComputeTileAndPixelShifts()                     */
2407
/************************************************************************/
2408
2409
bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2410
12
{
2411
12
    int nTileWidth, nTileHeight;
2412
12
    GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2413
2414
    // Compute shift between GDAL origin and TileMatrixSet origin
2415
12
    const double dfShiftXPixels =
2416
12
        (m_adfGeoTransform[0] - m_dfTMSMinX) / m_adfGeoTransform[1];
2417
12
    if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
2418
12
          dfShiftXPixels / nTileWidth < INT_MAX))
2419
0
    {
2420
0
        return false;
2421
0
    }
2422
12
    const int64_t nShiftXPixels =
2423
12
        static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2424
12
    m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2425
12
    if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2426
0
        m_nShiftXTiles--;
2427
12
    m_nShiftXPixelsMod =
2428
12
        (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2429
12
        nTileWidth;
2430
2431
12
    const double dfShiftYPixels =
2432
12
        (m_adfGeoTransform[3] - m_dfTMSMaxY) / m_adfGeoTransform[5];
2433
12
    if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
2434
12
          dfShiftYPixels / nTileHeight < INT_MAX))
2435
0
    {
2436
0
        return false;
2437
0
    }
2438
12
    const int64_t nShiftYPixels =
2439
12
        static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2440
12
    m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2441
12
    if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2442
0
        m_nShiftYTiles--;
2443
12
    m_nShiftYPixelsMod =
2444
12
        (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2445
12
        nTileHeight;
2446
12
    return true;
2447
12
}
2448
2449
/************************************************************************/
2450
/*                            AllocCachedTiles()                        */
2451
/************************************************************************/
2452
2453
bool GDALGeoPackageDataset::AllocCachedTiles()
2454
12
{
2455
12
    int nTileWidth, nTileHeight;
2456
12
    GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2457
2458
    // We currently need 4 caches because of
2459
    // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2460
12
    const int nCacheCount = 4;
2461
    /*
2462
            (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2463
            (GetUpdate() && m_eDT == GDT_Byte) ? 2 : 1;
2464
    */
2465
12
    m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2466
12
        cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_Byte ? 4 : 1) *
2467
12
                          m_nDTSize),
2468
12
        nTileWidth, nTileHeight));
2469
12
    if (m_pabyCachedTiles == nullptr)
2470
0
    {
2471
0
        CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2472
0
                 nTileWidth, nTileHeight);
2473
0
        return false;
2474
0
    }
2475
2476
12
    return true;
2477
12
}
2478
2479
/************************************************************************/
2480
/*                         InitRaster()                                 */
2481
/************************************************************************/
2482
2483
bool GDALGeoPackageDataset::InitRaster(
2484
    GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2485
    int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2486
    double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2487
    int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2488
    double dfGDALMaxX, double dfGDALMaxY)
2489
12
{
2490
12
    m_osRasterTable = pszTableName;
2491
12
    m_dfTMSMinX = dfTMSMinX;
2492
12
    m_dfTMSMaxY = dfTMSMaxY;
2493
12
    m_nZoomLevel = nZoomLevel;
2494
12
    m_nTileMatrixWidth = nTileMatrixWidth;
2495
12
    m_nTileMatrixHeight = nTileMatrixHeight;
2496
2497
12
    m_bGeoTransformValid = true;
2498
12
    m_adfGeoTransform[0] = dfGDALMinX;
2499
12
    m_adfGeoTransform[1] = dfPixelXSize;
2500
12
    m_adfGeoTransform[3] = dfGDALMaxY;
2501
12
    m_adfGeoTransform[5] = -dfPixelYSize;
2502
12
    double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2503
12
    double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2504
12
    if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2505
0
    {
2506
0
        CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2507
0
                 dfRasterXSize, dfRasterYSize);
2508
0
        return false;
2509
0
    }
2510
12
    nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2511
12
    nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2512
2513
12
    if (poParentDS)
2514
2
    {
2515
2
        m_poParentDS = poParentDS;
2516
2
        eAccess = poParentDS->eAccess;
2517
2
        hDB = poParentDS->hDB;
2518
2
        m_eTF = poParentDS->m_eTF;
2519
2
        m_eDT = poParentDS->m_eDT;
2520
2
        m_nDTSize = poParentDS->m_nDTSize;
2521
2
        m_dfScale = poParentDS->m_dfScale;
2522
2
        m_dfOffset = poParentDS->m_dfOffset;
2523
2
        m_dfPrecision = poParentDS->m_dfPrecision;
2524
2
        m_usGPKGNull = poParentDS->m_usGPKGNull;
2525
2
        m_nQuality = poParentDS->m_nQuality;
2526
2
        m_nZLevel = poParentDS->m_nZLevel;
2527
2
        m_bDither = poParentDS->m_bDither;
2528
        /*m_nSRID = poParentDS->m_nSRID;*/
2529
2
        m_osWHERE = poParentDS->m_osWHERE;
2530
2
        SetDescription(CPLSPrintf("%s - zoom_level=%d",
2531
2
                                  poParentDS->GetDescription(), m_nZoomLevel));
2532
2
    }
2533
2534
54
    for (int i = 1; i <= nBandCount; i++)
2535
42
    {
2536
42
        auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2537
42
            this, nTileWidth, nTileHeight);
2538
42
        if (poParentDS)
2539
8
        {
2540
8
            int bHasNoData = FALSE;
2541
8
            double dfNoDataValue =
2542
8
                poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2543
8
            if (bHasNoData)
2544
0
                poNewBand->SetNoDataValueInternal(dfNoDataValue);
2545
8
        }
2546
2547
42
        if (nBandCount == 1 && m_poCTFromMetadata)
2548
0
        {
2549
0
            poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2550
0
        }
2551
42
        if (!m_osNodataValueFromMetadata.empty())
2552
0
        {
2553
0
            poNewBand->SetNoDataValueInternal(
2554
0
                CPLAtof(m_osNodataValueFromMetadata.c_str()));
2555
0
        }
2556
2557
42
        SetBand(i, std::move(poNewBand));
2558
42
    }
2559
2560
12
    if (!ComputeTileAndPixelShifts())
2561
0
    {
2562
0
        CPLError(CE_Failure, CPLE_AppDefined,
2563
0
                 "Overflow occurred in ComputeTileAndPixelShifts()");
2564
0
        return false;
2565
0
    }
2566
2567
12
    GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2568
12
    GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2569
12
                                    CPLSPrintf("%d", m_nZoomLevel));
2570
2571
12
    return AllocCachedTiles();
2572
12
}
2573
2574
/************************************************************************/
2575
/*                 GDALGPKGMBTilesGetTileFormat()                       */
2576
/************************************************************************/
2577
2578
GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2579
0
{
2580
0
    GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2581
0
    if (pszTF)
2582
0
    {
2583
0
        if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2584
0
            eTF = GPKG_TF_PNG_JPEG;
2585
0
        else if (EQUAL(pszTF, "PNG"))
2586
0
            eTF = GPKG_TF_PNG;
2587
0
        else if (EQUAL(pszTF, "PNG8"))
2588
0
            eTF = GPKG_TF_PNG8;
2589
0
        else if (EQUAL(pszTF, "JPEG"))
2590
0
            eTF = GPKG_TF_JPEG;
2591
0
        else if (EQUAL(pszTF, "WEBP"))
2592
0
            eTF = GPKG_TF_WEBP;
2593
0
        else
2594
0
        {
2595
0
            CPLError(CE_Failure, CPLE_NotSupported,
2596
0
                     "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2597
0
        }
2598
0
    }
2599
0
    return eTF;
2600
0
}
2601
2602
const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2603
0
{
2604
0
    switch (eTF)
2605
0
    {
2606
0
        case GPKG_TF_PNG:
2607
0
        case GPKG_TF_PNG8:
2608
0
            return "png";
2609
0
        case GPKG_TF_JPEG:
2610
0
            return "jpg";
2611
0
        case GPKG_TF_WEBP:
2612
0
            return "webp";
2613
0
        default:
2614
0
            break;
2615
0
    }
2616
0
    CPLError(CE_Failure, CPLE_NotSupported,
2617
0
             "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2618
0
    return nullptr;
2619
0
}
2620
2621
/************************************************************************/
2622
/*                         OpenRaster()                                 */
2623
/************************************************************************/
2624
2625
bool GDALGeoPackageDataset::OpenRaster(
2626
    const char *pszTableName, const char *pszIdentifier,
2627
    const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2628
    double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2629
    const char *pszContentsMinY, const char *pszContentsMaxX,
2630
    const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2631
10
{
2632
10
    if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2633
0
        return false;
2634
2635
    // Config option just for debug, and for example force set to NaN
2636
    // which is not supported
2637
10
    CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2638
10
    CPLString osUom;
2639
10
    CPLString osFieldName;
2640
10
    CPLString osGridCellEncoding;
2641
10
    if (!bIsTiles)
2642
2
    {
2643
2
        char *pszSQL = sqlite3_mprintf(
2644
2
            "SELECT datatype, scale, offset, data_null, precision FROM "
2645
2
            "gpkg_2d_gridded_coverage_ancillary "
2646
2
            "WHERE tile_matrix_set_name = '%q' "
2647
2
            "AND datatype IN ('integer', 'float')"
2648
2
            "AND (scale > 0 OR scale IS NULL)",
2649
2
            pszTableName);
2650
2
        auto oResult = SQLQuery(hDB, pszSQL);
2651
2
        sqlite3_free(pszSQL);
2652
2
        if (!oResult || oResult->RowCount() == 0)
2653
0
        {
2654
0
            return false;
2655
0
        }
2656
2
        const char *pszDataType = oResult->GetValue(0, 0);
2657
2
        const char *pszScale = oResult->GetValue(1, 0);
2658
2
        const char *pszOffset = oResult->GetValue(2, 0);
2659
2
        const char *pszDataNull = oResult->GetValue(3, 0);
2660
2
        const char *pszPrecision = oResult->GetValue(4, 0);
2661
2
        if (pszDataNull)
2662
0
            osDataNull = pszDataNull;
2663
2
        if (EQUAL(pszDataType, "float"))
2664
0
        {
2665
0
            SetDataType(GDT_Float32);
2666
0
            m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2667
0
        }
2668
2
        else
2669
2
        {
2670
2
            SetDataType(GDT_Float32);
2671
2
            m_eTF = GPKG_TF_PNG_16BIT;
2672
2
            const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2673
2
            const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2674
2
            if (dfScale == 1.0)
2675
2
            {
2676
2
                if (dfOffset == 0.0)
2677
2
                {
2678
2
                    SetDataType(GDT_UInt16);
2679
2
                }
2680
0
                else if (dfOffset == -32768.0)
2681
0
                {
2682
0
                    SetDataType(GDT_Int16);
2683
0
                }
2684
                // coverity[tainted_data]
2685
0
                else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2686
0
                         CPLAtof(osDataNull) == 65535.0)
2687
                // Given that we will map the nodata value to -32768
2688
0
                {
2689
0
                    SetDataType(GDT_Int16);
2690
0
                }
2691
2
            }
2692
2693
            // Check that the tile offset and scales are compatible of a
2694
            // final integer result.
2695
2
            if (m_eDT != GDT_Float32)
2696
2
            {
2697
                // coverity[tainted_data]
2698
2
                if (dfScale == 1.0 && dfOffset == -32768.0 &&
2699
2
                    !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2700
0
                {
2701
                    // Given that we will map the nodata value to -32768
2702
0
                    pszSQL = sqlite3_mprintf(
2703
0
                        "SELECT 1 FROM "
2704
0
                        "gpkg_2d_gridded_tile_ancillary WHERE "
2705
0
                        "tpudt_name = '%q' "
2706
0
                        "AND NOT ((offset = 0.0 or offset = 1.0) "
2707
0
                        "AND scale = 1.0) "
2708
0
                        "LIMIT 1",
2709
0
                        pszTableName);
2710
0
                }
2711
2
                else
2712
2
                {
2713
2
                    pszSQL = sqlite3_mprintf(
2714
2
                        "SELECT 1 FROM "
2715
2
                        "gpkg_2d_gridded_tile_ancillary WHERE "
2716
2
                        "tpudt_name = '%q' "
2717
2
                        "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2718
2
                        pszTableName);
2719
2
                }
2720
2
                sqlite3_stmt *hSQLStmt = nullptr;
2721
2
                int rc =
2722
2
                    SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2723
2724
2
                if (rc == SQLITE_OK)
2725
2
                {
2726
2
                    if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2727
0
                    {
2728
0
                        SetDataType(GDT_Float32);
2729
0
                    }
2730
2
                    sqlite3_finalize(hSQLStmt);
2731
2
                }
2732
2
                sqlite3_free(pszSQL);
2733
2
            }
2734
2735
2
            SetGlobalOffsetScale(dfOffset, dfScale);
2736
2
        }
2737
2
        if (pszPrecision)
2738
2
            m_dfPrecision = CPLAtof(pszPrecision);
2739
2740
        // Request those columns in a separate query, so as to keep
2741
        // compatibility with pre OGC 17-066r1 databases
2742
2
        pszSQL =
2743
2
            sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2744
2
                            "gpkg_2d_gridded_coverage_ancillary "
2745
2
                            "WHERE tile_matrix_set_name = '%q'",
2746
2
                            pszTableName);
2747
2
        CPLPushErrorHandler(CPLQuietErrorHandler);
2748
2
        oResult = SQLQuery(hDB, pszSQL);
2749
2
        CPLPopErrorHandler();
2750
2
        sqlite3_free(pszSQL);
2751
2
        if (oResult && oResult->RowCount() == 1)
2752
0
        {
2753
0
            const char *pszUom = oResult->GetValue(0, 0);
2754
0
            if (pszUom)
2755
0
                osUom = pszUom;
2756
0
            const char *pszFieldName = oResult->GetValue(1, 0);
2757
0
            if (pszFieldName)
2758
0
                osFieldName = pszFieldName;
2759
0
            const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2760
0
            if (pszGridCellEncoding)
2761
0
                osGridCellEncoding = pszGridCellEncoding;
2762
0
        }
2763
2
    }
2764
2765
10
    m_bRecordInsertedInGPKGContent = true;
2766
10
    m_nSRID = nSRSId;
2767
2768
10
    if (auto poSRS = GetSpatialRef(nSRSId))
2769
8
    {
2770
8
        m_oSRS = *(poSRS.get());
2771
8
    }
2772
2773
    /* Various sanity checks added in the SELECT */
2774
10
    char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2775
10
    CPLString osQuotedTableName(pszQuotedTableName);
2776
10
    sqlite3_free(pszQuotedTableName);
2777
10
    char *pszSQL = sqlite3_mprintf(
2778
10
        "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2779
10
        "tile_height, matrix_width, matrix_height "
2780
10
        "FROM gpkg_tile_matrix tm "
2781
10
        "WHERE table_name = %s "
2782
        // INT_MAX would be the theoretical maximum value to avoid
2783
        // overflows, but that's already a insane value.
2784
10
        "AND zoom_level >= 0 AND zoom_level <= 65536 "
2785
10
        "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2786
10
        "AND tile_width >= 1 AND tile_width <= 65536 "
2787
10
        "AND tile_height >= 1 AND tile_height <= 65536 "
2788
10
        "AND matrix_width >= 1 AND matrix_height >= 1",
2789
10
        osQuotedTableName.c_str());
2790
10
    CPLString osSQL(pszSQL);
2791
10
    const char *pszZoomLevel =
2792
10
        CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2793
10
    if (pszZoomLevel)
2794
0
    {
2795
0
        if (GetUpdate())
2796
0
            osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2797
0
        else
2798
0
        {
2799
0
            osSQL += CPLSPrintf(
2800
0
                " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2801
0
                "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2802
0
                atoi(pszZoomLevel), atoi(pszZoomLevel),
2803
0
                osQuotedTableName.c_str());
2804
0
        }
2805
0
    }
2806
    // In read-only mode, only lists non empty zoom levels
2807
10
    else if (!GetUpdate())
2808
10
    {
2809
10
        osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2810
10
                            "tm.zoom_level LIMIT 1)",
2811
10
                            osQuotedTableName.c_str());
2812
10
    }
2813
0
    else  // if( pszZoomLevel == nullptr )
2814
0
    {
2815
0
        osSQL +=
2816
0
            CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2817
0
                       osQuotedTableName.c_str());
2818
0
    }
2819
10
    osSQL += " ORDER BY zoom_level DESC";
2820
    // To avoid denial of service.
2821
10
    osSQL += " LIMIT 100";
2822
2823
10
    auto oResult = SQLQuery(hDB, osSQL.c_str());
2824
10
    if (!oResult || oResult->RowCount() == 0)
2825
2
    {
2826
2
        if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2827
2
            pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2828
2
            pszContentsMaxY != nullptr)
2829
2
        {
2830
2
            osSQL = pszSQL;
2831
2
            osSQL += " ORDER BY zoom_level DESC";
2832
2
            if (!GetUpdate())
2833
2
                osSQL += " LIMIT 1";
2834
2
            oResult = SQLQuery(hDB, osSQL.c_str());
2835
2
        }
2836
2
        if (!oResult || oResult->RowCount() == 0)
2837
0
        {
2838
0
            if (oResult && pszZoomLevel != nullptr)
2839
0
            {
2840
0
                CPLError(CE_Failure, CPLE_AppDefined,
2841
0
                         "ZOOM_LEVEL is probably not valid w.r.t tile "
2842
0
                         "table content");
2843
0
            }
2844
0
            sqlite3_free(pszSQL);
2845
0
            return false;
2846
0
        }
2847
2
    }
2848
10
    sqlite3_free(pszSQL);
2849
2850
    // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2851
    // actually exist.
2852
2853
    // CAUTION: Do not move those variables inside inner scope !
2854
10
    CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2855
2856
10
    if (CPLTestBool(
2857
10
            CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2858
0
    {
2859
0
        pszSQL = sqlite3_mprintf(
2860
0
            "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2861
0
            "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2862
0
            pszTableName, atoi(oResult->GetValue(0, 0)));
2863
0
        auto oResult2 = SQLQuery(hDB, pszSQL);
2864
0
        sqlite3_free(pszSQL);
2865
0
        if (!oResult2 || oResult2->RowCount() == 0 ||
2866
            // Can happen if table is empty
2867
0
            oResult2->GetValue(0, 0) == nullptr ||
2868
            // Can happen if table has no NOT NULL constraint on tile_row
2869
            // and that all tile_row are NULL
2870
0
            oResult2->GetValue(1, 0) == nullptr)
2871
0
        {
2872
0
            return false;
2873
0
        }
2874
0
        const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2875
0
        const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2876
0
        const int nTileWidth = atoi(oResult->GetValue(3, 0));
2877
0
        const int nTileHeight = atoi(oResult->GetValue(4, 0));
2878
0
        osContentsMinX =
2879
0
            CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2880
0
                                             atoi(oResult2->GetValue(0, 0)));
2881
0
        osContentsMaxY =
2882
0
            CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2883
0
                                             atoi(oResult2->GetValue(1, 0)));
2884
0
        osContentsMaxX = CPLSPrintf(
2885
0
            "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2886
0
                                  (1 + atoi(oResult2->GetValue(2, 0))));
2887
0
        osContentsMinY = CPLSPrintf(
2888
0
            "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2889
0
                                  (1 + atoi(oResult2->GetValue(3, 0))));
2890
0
        pszContentsMinX = osContentsMinX.c_str();
2891
0
        pszContentsMinY = osContentsMinY.c_str();
2892
0
        pszContentsMaxX = osContentsMaxX.c_str();
2893
0
        pszContentsMaxY = osContentsMaxY.c_str();
2894
0
    }
2895
2896
10
    if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2897
10
                    pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2898
10
                    pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2899
0
    {
2900
0
        return false;
2901
0
    }
2902
2903
10
    auto poBand =
2904
10
        reinterpret_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2905
10
    if (!osDataNull.empty())
2906
0
    {
2907
0
        double dfGPKGNoDataValue = CPLAtof(osDataNull);
2908
0
        if (m_eTF == GPKG_TF_PNG_16BIT)
2909
0
        {
2910
0
            if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2911
0
                static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2912
0
            {
2913
0
                CPLError(CE_Warning, CPLE_AppDefined,
2914
0
                         "data_null = %.17g is invalid for integer data_type",
2915
0
                         dfGPKGNoDataValue);
2916
0
            }
2917
0
            else
2918
0
            {
2919
0
                m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2920
0
                if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2921
0
                    dfGPKGNoDataValue = -32768.0;
2922
0
                else if (m_eDT == GDT_Float32)
2923
0
                {
2924
                    // Pick a value that is unlikely to be hit with offset &
2925
                    // scale
2926
0
                    dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2927
0
                }
2928
0
                poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2929
0
            }
2930
0
        }
2931
0
        else
2932
0
        {
2933
0
            poBand->SetNoDataValueInternal(
2934
0
                static_cast<float>(dfGPKGNoDataValue));
2935
0
        }
2936
0
    }
2937
10
    if (!osUom.empty())
2938
0
    {
2939
0
        poBand->SetUnitTypeInternal(osUom);
2940
0
    }
2941
10
    if (!osFieldName.empty())
2942
0
    {
2943
0
        GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2944
0
    }
2945
10
    if (!osGridCellEncoding.empty())
2946
0
    {
2947
0
        if (osGridCellEncoding == "grid-value-is-center")
2948
0
        {
2949
0
            GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2950
0
                                            GDALMD_AOP_POINT);
2951
0
        }
2952
0
        else if (osGridCellEncoding == "grid-value-is-area")
2953
0
        {
2954
0
            GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2955
0
                                            GDALMD_AOP_AREA);
2956
0
        }
2957
0
        else
2958
0
        {
2959
0
            GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2960
0
                                            GDALMD_AOP_POINT);
2961
0
            GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2962
0
                "GRID_CELL_ENCODING", osGridCellEncoding);
2963
0
        }
2964
0
    }
2965
2966
10
    CheckUnknownExtensions(true);
2967
2968
    // Do this after CheckUnknownExtensions() so that m_eTF is set to
2969
    // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2970
10
    const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2971
10
    if (pszTF)
2972
0
    {
2973
0
        if (!GetUpdate())
2974
0
        {
2975
0
            CPLError(CE_Warning, CPLE_AppDefined,
2976
0
                     "TILE_FORMAT open option ignored in read-only mode");
2977
0
        }
2978
0
        else if (m_eTF == GPKG_TF_PNG_16BIT ||
2979
0
                 m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2980
0
        {
2981
0
            CPLError(CE_Warning, CPLE_AppDefined,
2982
0
                     "TILE_FORMAT open option ignored on gridded coverages");
2983
0
        }
2984
0
        else
2985
0
        {
2986
0
            GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2987
0
            if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2988
0
            {
2989
0
                if (!RegisterWebPExtension())
2990
0
                    return false;
2991
0
            }
2992
0
            m_eTF = eTF;
2993
0
        }
2994
0
    }
2995
2996
10
    ParseCompressionOptions(papszOpenOptionsIn);
2997
2998
10
    m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
2999
3000
    // Set metadata
3001
10
    if (pszIdentifier && pszIdentifier[0])
3002
10
        GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3003
10
    if (pszDescription && pszDescription[0])
3004
0
        GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3005
3006
    // Add overviews
3007
10
    for (int i = 1; i < oResult->RowCount(); i++)
3008
2
    {
3009
2
        auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3010
2
        poOvrDS->ShareLockWithParentDataset(this);
3011
2
        if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3012
2
                                 dfMaxY, pszContentsMinX, pszContentsMinY,
3013
2
                                 pszContentsMaxX, pszContentsMaxY,
3014
2
                                 papszOpenOptionsIn, *oResult, i))
3015
0
        {
3016
0
            break;
3017
0
        }
3018
3019
2
        int nTileWidth, nTileHeight;
3020
2
        poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3021
2
        const bool bStop =
3022
2
            (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3023
2
             poOvrDS->GetRasterYSize() < nTileHeight);
3024
3025
2
        m_apoOverviewDS.push_back(std::move(poOvrDS));
3026
3027
2
        if (bStop)
3028
2
        {
3029
2
            break;
3030
2
        }
3031
2
    }
3032
3033
10
    return true;
3034
10
}
3035
3036
/************************************************************************/
3037
/*                           GetSpatialRef()                            */
3038
/************************************************************************/
3039
3040
const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3041
13
{
3042
13
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3043
13
}
3044
3045
/************************************************************************/
3046
/*                           SetSpatialRef()                            */
3047
/************************************************************************/
3048
3049
CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3050
0
{
3051
0
    if (nBands == 0)
3052
0
    {
3053
0
        CPLError(CE_Failure, CPLE_NotSupported,
3054
0
                 "SetProjection() not supported on a dataset with 0 band");
3055
0
        return CE_Failure;
3056
0
    }
3057
0
    if (eAccess != GA_Update)
3058
0
    {
3059
0
        CPLError(CE_Failure, CPLE_NotSupported,
3060
0
                 "SetProjection() not supported on read-only dataset");
3061
0
        return CE_Failure;
3062
0
    }
3063
3064
0
    const int nSRID = GetSrsId(poSRS);
3065
0
    const auto poTS = GetTilingScheme(m_osTilingScheme);
3066
0
    if (poTS && nSRID != poTS->nEPSGCode)
3067
0
    {
3068
0
        CPLError(CE_Failure, CPLE_NotSupported,
3069
0
                 "Projection should be EPSG:%d for %s tiling scheme",
3070
0
                 poTS->nEPSGCode, m_osTilingScheme.c_str());
3071
0
        return CE_Failure;
3072
0
    }
3073
3074
0
    m_nSRID = nSRID;
3075
0
    m_oSRS.Clear();
3076
0
    if (poSRS)
3077
0
        m_oSRS = *poSRS;
3078
3079
0
    if (m_bRecordInsertedInGPKGContent)
3080
0
    {
3081
0
        char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3082
0
                                       "WHERE lower(table_name) = lower('%q')",
3083
0
                                       m_nSRID, m_osRasterTable.c_str());
3084
0
        OGRErr eErr = SQLCommand(hDB, pszSQL);
3085
0
        sqlite3_free(pszSQL);
3086
0
        if (eErr != OGRERR_NONE)
3087
0
            return CE_Failure;
3088
3089
0
        pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3090
0
                                 "WHERE lower(table_name) = lower('%q')",
3091
0
                                 m_nSRID, m_osRasterTable.c_str());
3092
0
        eErr = SQLCommand(hDB, pszSQL);
3093
0
        sqlite3_free(pszSQL);
3094
0
        if (eErr != OGRERR_NONE)
3095
0
            return CE_Failure;
3096
0
    }
3097
3098
0
    return CE_None;
3099
0
}
3100
3101
/************************************************************************/
3102
/*                          GetGeoTransform()                           */
3103
/************************************************************************/
3104
3105
CPLErr GDALGeoPackageDataset::GetGeoTransform(double *padfGeoTransform)
3106
13
{
3107
13
    memcpy(padfGeoTransform, m_adfGeoTransform.data(), 6 * sizeof(double));
3108
13
    if (!m_bGeoTransformValid)
3109
3
        return CE_Failure;
3110
10
    else
3111
10
        return CE_None;
3112
13
}
3113
3114
/************************************************************************/
3115
/*                          SetGeoTransform()                           */
3116
/************************************************************************/
3117
3118
CPLErr GDALGeoPackageDataset::SetGeoTransform(double *padfGeoTransform)
3119
0
{
3120
0
    if (nBands == 0)
3121
0
    {
3122
0
        CPLError(CE_Failure, CPLE_NotSupported,
3123
0
                 "SetGeoTransform() not supported on a dataset with 0 band");
3124
0
        return CE_Failure;
3125
0
    }
3126
0
    if (eAccess != GA_Update)
3127
0
    {
3128
0
        CPLError(CE_Failure, CPLE_NotSupported,
3129
0
                 "SetGeoTransform() not supported on read-only dataset");
3130
0
        return CE_Failure;
3131
0
    }
3132
0
    if (m_bGeoTransformValid)
3133
0
    {
3134
0
        CPLError(CE_Failure, CPLE_NotSupported,
3135
0
                 "Cannot modify geotransform once set");
3136
0
        return CE_Failure;
3137
0
    }
3138
0
    if (padfGeoTransform[2] != 0.0 || padfGeoTransform[4] != 0 ||
3139
0
        padfGeoTransform[5] > 0.0)
3140
0
    {
3141
0
        CPLError(CE_Failure, CPLE_NotSupported,
3142
0
                 "Only north-up non rotated geotransform supported");
3143
0
        return CE_Failure;
3144
0
    }
3145
3146
0
    if (m_nZoomLevel < 0)
3147
0
    {
3148
0
        const auto poTS = GetTilingScheme(m_osTilingScheme);
3149
0
        if (poTS)
3150
0
        {
3151
0
            double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3152
0
            double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3153
0
            for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3154
0
                 m_nZoomLevel++)
3155
0
            {
3156
0
                double dfExpectedPixelXSize =
3157
0
                    dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3158
0
                double dfExpectedPixelYSize =
3159
0
                    dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3160
0
                if (fabs(padfGeoTransform[1] - dfExpectedPixelXSize) <
3161
0
                        1e-8 * dfExpectedPixelXSize &&
3162
0
                    fabs(fabs(padfGeoTransform[5]) - dfExpectedPixelYSize) <
3163
0
                        1e-8 * dfExpectedPixelYSize)
3164
0
                {
3165
0
                    break;
3166
0
                }
3167
0
            }
3168
0
            if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3169
0
            {
3170
0
                m_nZoomLevel = -1;
3171
0
                CPLError(
3172
0
                    CE_Failure, CPLE_NotSupported,
3173
0
                    "Could not find an appropriate zoom level of %s tiling "
3174
0
                    "scheme that matches raster pixel size",
3175
0
                    m_osTilingScheme.c_str());
3176
0
                return CE_Failure;
3177
0
            }
3178
0
        }
3179
0
    }
3180
3181
0
    memcpy(m_adfGeoTransform.data(), padfGeoTransform, 6 * sizeof(double));
3182
0
    m_bGeoTransformValid = true;
3183
3184
0
    return FinalizeRasterRegistration();
3185
0
}
3186
3187
/************************************************************************/
3188
/*                      FinalizeRasterRegistration()                    */
3189
/************************************************************************/
3190
3191
CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3192
0
{
3193
0
    OGRErr eErr;
3194
3195
0
    m_dfTMSMinX = m_adfGeoTransform[0];
3196
0
    m_dfTMSMaxY = m_adfGeoTransform[3];
3197
3198
0
    int nTileWidth, nTileHeight;
3199
0
    GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3200
3201
0
    if (m_nZoomLevel < 0)
3202
0
    {
3203
0
        m_nZoomLevel = 0;
3204
0
        while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3205
0
               (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3206
0
            m_nZoomLevel++;
3207
0
    }
3208
3209
0
    double dfPixelXSizeZoomLevel0 = m_adfGeoTransform[1] * (1 << m_nZoomLevel);
3210
0
    double dfPixelYSizeZoomLevel0 =
3211
0
        fabs(m_adfGeoTransform[5]) * (1 << m_nZoomLevel);
3212
0
    int nTileXCountZoomLevel0 =
3213
0
        std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3214
0
    int nTileYCountZoomLevel0 =
3215
0
        std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3216
3217
0
    const auto poTS = GetTilingScheme(m_osTilingScheme);
3218
0
    if (poTS)
3219
0
    {
3220
0
        CPLAssert(m_nZoomLevel >= 0);
3221
0
        m_dfTMSMinX = poTS->dfMinX;
3222
0
        m_dfTMSMaxY = poTS->dfMaxY;
3223
0
        dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3224
0
        dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3225
0
        nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3226
0
        nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3227
0
    }
3228
0
    m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3229
0
    m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3230
3231
0
    if (!ComputeTileAndPixelShifts())
3232
0
    {
3233
0
        CPLError(CE_Failure, CPLE_AppDefined,
3234
0
                 "Overflow occurred in ComputeTileAndPixelShifts()");
3235
0
        return CE_Failure;
3236
0
    }
3237
3238
0
    if (!AllocCachedTiles())
3239
0
    {
3240
0
        return CE_Failure;
3241
0
    }
3242
3243
0
    double dfGDALMinX = m_adfGeoTransform[0];
3244
0
    double dfGDALMinY =
3245
0
        m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3246
0
    double dfGDALMaxX =
3247
0
        m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3248
0
    double dfGDALMaxY = m_adfGeoTransform[3];
3249
3250
0
    if (SoftStartTransaction() != OGRERR_NONE)
3251
0
        return CE_Failure;
3252
3253
0
    const char *pszCurrentDate =
3254
0
        CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3255
0
    CPLString osInsertGpkgContentsFormatting(
3256
0
        "INSERT INTO gpkg_contents "
3257
0
        "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3258
0
        "last_change,srs_id) VALUES "
3259
0
        "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3260
0
    osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3261
0
    osInsertGpkgContentsFormatting += ",%d)";
3262
0
    char *pszSQL = sqlite3_mprintf(
3263
0
        osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3264
0
        (m_eDT == GDT_Byte) ? "tiles" : "2d-gridded-coverage",
3265
0
        m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3266
0
        dfGDALMaxX, dfGDALMaxY,
3267
0
        pszCurrentDate ? pszCurrentDate
3268
0
                       : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3269
0
        m_nSRID);
3270
3271
0
    eErr = SQLCommand(hDB, pszSQL);
3272
0
    sqlite3_free(pszSQL);
3273
0
    if (eErr != OGRERR_NONE)
3274
0
    {
3275
0
        SoftRollbackTransaction();
3276
0
        return CE_Failure;
3277
0
    }
3278
3279
0
    double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3280
0
                                         dfPixelXSizeZoomLevel0;
3281
0
    double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3282
0
                                         dfPixelYSizeZoomLevel0;
3283
3284
0
    pszSQL =
3285
0
        sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3286
0
                        "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3287
0
                        "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3288
0
                        m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3289
0
                        dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3290
0
    eErr = SQLCommand(hDB, pszSQL);
3291
0
    sqlite3_free(pszSQL);
3292
0
    if (eErr != OGRERR_NONE)
3293
0
    {
3294
0
        SoftRollbackTransaction();
3295
0
        return CE_Failure;
3296
0
    }
3297
3298
0
    m_apoOverviewDS.resize(m_nZoomLevel);
3299
3300
0
    for (int i = 0; i <= m_nZoomLevel; i++)
3301
0
    {
3302
0
        double dfPixelXSizeZoomLevel = 0.0;
3303
0
        double dfPixelYSizeZoomLevel = 0.0;
3304
0
        int nTileMatrixWidth = 0;
3305
0
        int nTileMatrixHeight = 0;
3306
0
        if (EQUAL(m_osTilingScheme, "CUSTOM"))
3307
0
        {
3308
0
            dfPixelXSizeZoomLevel =
3309
0
                m_adfGeoTransform[1] * (1 << (m_nZoomLevel - i));
3310
0
            dfPixelYSizeZoomLevel =
3311
0
                fabs(m_adfGeoTransform[5]) * (1 << (m_nZoomLevel - i));
3312
0
        }
3313
0
        else
3314
0
        {
3315
0
            dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3316
0
            dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3317
0
        }
3318
0
        nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3319
0
        nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3320
3321
0
        pszSQL = sqlite3_mprintf(
3322
0
            "INSERT INTO gpkg_tile_matrix "
3323
0
            "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3324
0
            "height,pixel_x_size,pixel_y_size) VALUES "
3325
0
            "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3326
0
            m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3327
0
            nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3328
0
            dfPixelYSizeZoomLevel);
3329
0
        eErr = SQLCommand(hDB, pszSQL);
3330
0
        sqlite3_free(pszSQL);
3331
0
        if (eErr != OGRERR_NONE)
3332
0
        {
3333
0
            SoftRollbackTransaction();
3334
0
            return CE_Failure;
3335
0
        }
3336
3337
0
        if (i < m_nZoomLevel)
3338
0
        {
3339
0
            auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3340
0
            poOvrDS->ShareLockWithParentDataset(this);
3341
0
            poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3342
0
                                m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3343
0
                                dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3344
0
                                nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3345
0
                                dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3346
3347
0
            m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3348
0
        }
3349
0
    }
3350
3351
0
    if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3352
0
    {
3353
0
        eErr = SQLCommand(
3354
0
            hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3355
0
        m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3356
0
        if (eErr != OGRERR_NONE)
3357
0
        {
3358
0
            SoftRollbackTransaction();
3359
0
            return CE_Failure;
3360
0
        }
3361
0
    }
3362
3363
0
    SoftCommitTransaction();
3364
3365
0
    m_apoOverviewDS.resize(m_nZoomLevel);
3366
0
    m_bRecordInsertedInGPKGContent = true;
3367
3368
0
    return CE_None;
3369
0
}
3370
3371
/************************************************************************/
3372
/*                             FlushCache()                             */
3373
/************************************************************************/
3374
3375
CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3376
2.59k
{
3377
2.59k
    if (m_bInFlushCache)
3378
0
        return CE_None;
3379
3380
2.59k
    if (eAccess == GA_Update || !m_bMetadataDirty)
3381
2.59k
    {
3382
2.59k
        SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3383
2.59k
    }
3384
3385
2.59k
    if (m_bRemoveOGREmptyTable)
3386
253
    {
3387
253
        m_bRemoveOGREmptyTable = false;
3388
253
        RemoveOGREmptyTable();
3389
253
    }
3390
3391
2.59k
    CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3392
3393
2.59k
    FlushMetadata();
3394
3395
2.59k
    if (eAccess == GA_Update || !m_bMetadataDirty)
3396
2.59k
    {
3397
        // Needed again as above IFlushCacheWithErrCode()
3398
        // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3399
        // which modifies metadata
3400
2.59k
        SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3401
2.59k
    }
3402
3403
2.59k
    return eErr;
3404
2.59k
}
3405
3406
CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3407
3408
2.63k
{
3409
2.63k
    if (m_bInFlushCache)
3410
42
        return CE_None;
3411
2.59k
    m_bInFlushCache = true;
3412
2.59k
    if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3413
2.02k
    {
3414
        // Clean-up metadata that will go to PAM by removing items that
3415
        // are reconstructed.
3416
2.02k
        CPLStringList aosMD;
3417
2.08k
        for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3418
2.02k
             ++papszIter)
3419
66
        {
3420
66
            char *pszKey = nullptr;
3421
66
            CPLParseNameValue(*papszIter, &pszKey);
3422
66
            if (pszKey &&
3423
66
                (EQUAL(pszKey, "AREA_OR_POINT") ||
3424
66
                 EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3425
66
                 EQUAL(pszKey, "ZOOM_LEVEL") ||
3426
66
                 STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3427
22
            {
3428
                // remove it
3429
22
            }
3430
44
            else
3431
44
            {
3432
44
                aosMD.AddString(*papszIter);
3433
44
            }
3434
66
            CPLFree(pszKey);
3435
66
        }
3436
2.02k
        oMDMD.SetMetadata(aosMD.List());
3437
2.02k
        oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3438
3439
2.02k
        GDALPamDataset::FlushCache(bAtClosing);
3440
2.02k
    }
3441
572
    else
3442
572
    {
3443
        // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3444
572
        GDALDataset::FlushCache(bAtClosing);
3445
572
    }
3446
3447
2.59k
    for (auto &poLayer : m_apoLayers)
3448
4.82k
    {
3449
4.82k
        poLayer->RunDeferredCreationIfNecessary();
3450
4.82k
        poLayer->CreateSpatialIndexIfNecessary();
3451
4.82k
    }
3452
3453
    // Update raster table last_change column in gpkg_contents if needed
3454
2.59k
    if (m_bHasModifiedTiles)
3455
0
    {
3456
0
        for (int i = 1; i <= nBands; ++i)
3457
0
        {
3458
0
            auto poBand =
3459
0
                cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3460
0
            if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3461
0
            {
3462
0
                poBand->InvalidateStatistics();
3463
0
                if (psPam && psPam->pszPamFilename)
3464
0
                    VSIUnlink(psPam->pszPamFilename);
3465
0
            }
3466
0
        }
3467
3468
0
        UpdateGpkgContentsLastChange(m_osRasterTable);
3469
3470
0
        m_bHasModifiedTiles = false;
3471
0
    }
3472
3473
2.59k
    CPLErr eErr = FlushTiles();
3474
3475
2.59k
    m_bInFlushCache = false;
3476
2.59k
    return eErr;
3477
2.63k
}
3478
3479
/************************************************************************/
3480
/*                       GetCurrentDateEscapedSQL()                      */
3481
/************************************************************************/
3482
3483
std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3484
3.13k
{
3485
3.13k
    const char *pszCurrentDate =
3486
3.13k
        CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3487
3.13k
    if (pszCurrentDate)
3488
0
        return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3489
3.13k
    return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3490
3.13k
}
3491
3492
/************************************************************************/
3493
/*                    UpdateGpkgContentsLastChange()                    */
3494
/************************************************************************/
3495
3496
OGRErr
3497
GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3498
1.48k
{
3499
1.48k
    char *pszSQL =
3500
1.48k
        sqlite3_mprintf("UPDATE gpkg_contents SET "
3501
1.48k
                        "last_change = %s "
3502
1.48k
                        "WHERE lower(table_name) = lower('%q')",
3503
1.48k
                        GetCurrentDateEscapedSQL().c_str(), pszTableName);
3504
1.48k
    OGRErr eErr = SQLCommand(hDB, pszSQL);
3505
1.48k
    sqlite3_free(pszSQL);
3506
1.48k
    return eErr;
3507
1.48k
}
3508
3509
/************************************************************************/
3510
/*                          IBuildOverviews()                           */
3511
/************************************************************************/
3512
3513
CPLErr GDALGeoPackageDataset::IBuildOverviews(
3514
    const char *pszResampling, int nOverviews, const int *panOverviewList,
3515
    int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3516
    void *pProgressData, CSLConstList papszOptions)
3517
0
{
3518
0
    if (GetAccess() != GA_Update)
3519
0
    {
3520
0
        CPLError(CE_Failure, CPLE_NotSupported,
3521
0
                 "Overview building not supported on a database opened in "
3522
0
                 "read-only mode");
3523
0
        return CE_Failure;
3524
0
    }
3525
0
    if (m_poParentDS != nullptr)
3526
0
    {
3527
0
        CPLError(CE_Failure, CPLE_NotSupported,
3528
0
                 "Overview building not supported on overview dataset");
3529
0
        return CE_Failure;
3530
0
    }
3531
3532
0
    if (nOverviews == 0)
3533
0
    {
3534
0
        for (auto &poOvrDS : m_apoOverviewDS)
3535
0
            poOvrDS->FlushCache(false);
3536
3537
0
        SoftStartTransaction();
3538
3539
0
        if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3540
0
        {
3541
0
            char *pszSQL = sqlite3_mprintf(
3542
0
                "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3543
0
                "(SELECT y.id FROM \"%w\" x "
3544
0
                "JOIN gpkg_2d_gridded_tile_ancillary y "
3545
0
                "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3546
0
                "x.zoom_level < %d)",
3547
0
                m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3548
0
            OGRErr eErr = SQLCommand(hDB, pszSQL);
3549
0
            sqlite3_free(pszSQL);
3550
0
            if (eErr != OGRERR_NONE)
3551
0
            {
3552
0
                SoftRollbackTransaction();
3553
0
                return CE_Failure;
3554
0
            }
3555
0
        }
3556
3557
0
        char *pszSQL =
3558
0
            sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3559
0
                            m_osRasterTable.c_str(), m_nZoomLevel);
3560
0
        OGRErr eErr = SQLCommand(hDB, pszSQL);
3561
0
        sqlite3_free(pszSQL);
3562
0
        if (eErr != OGRERR_NONE)
3563
0
        {
3564
0
            SoftRollbackTransaction();
3565
0
            return CE_Failure;
3566
0
        }
3567
3568
0
        SoftCommitTransaction();
3569
3570
0
        return CE_None;
3571
0
    }
3572
3573
0
    if (nBandsIn != nBands)
3574
0
    {
3575
0
        CPLError(CE_Failure, CPLE_NotSupported,
3576
0
                 "Generation of overviews in GPKG only"
3577
0
                 "supported when operating on all bands.");
3578
0
        return CE_Failure;
3579
0
    }
3580
3581
0
    if (m_apoOverviewDS.empty())
3582
0
    {
3583
0
        CPLError(CE_Failure, CPLE_AppDefined,
3584
0
                 "Image too small to support overviews");
3585
0
        return CE_Failure;
3586
0
    }
3587
3588
0
    FlushCache(false);
3589
0
    for (int i = 0; i < nOverviews; i++)
3590
0
    {
3591
0
        if (panOverviewList[i] < 2)
3592
0
        {
3593
0
            CPLError(CE_Failure, CPLE_IllegalArg,
3594
0
                     "Overview factor must be >= 2");
3595
0
            return CE_Failure;
3596
0
        }
3597
3598
0
        bool bFound = false;
3599
0
        int jCandidate = -1;
3600
0
        int nMaxOvFactor = 0;
3601
0
        for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3602
0
        {
3603
0
            const auto poODS = m_apoOverviewDS[j].get();
3604
0
            const int nOvFactor = static_cast<int>(
3605
0
                0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3606
3607
0
            nMaxOvFactor = nOvFactor;
3608
3609
0
            if (nOvFactor == panOverviewList[i])
3610
0
            {
3611
0
                bFound = true;
3612
0
                break;
3613
0
            }
3614
3615
0
            if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3616
0
                jCandidate = j;
3617
0
        }
3618
3619
0
        if (!bFound)
3620
0
        {
3621
            /* Mostly for debug */
3622
0
            if (!CPLTestBool(CPLGetConfigOption(
3623
0
                    "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3624
0
            {
3625
0
                CPLString osOvrList;
3626
0
                for (const auto &poODS : m_apoOverviewDS)
3627
0
                {
3628
0
                    const int nOvFactor =
3629
0
                        static_cast<int>(0.5 + poODS->m_adfGeoTransform[1] /
3630
0
                                                   m_adfGeoTransform[1]);
3631
3632
0
                    if (!osOvrList.empty())
3633
0
                        osOvrList += ' ';
3634
0
                    osOvrList += CPLSPrintf("%d", nOvFactor);
3635
0
                }
3636
0
                CPLError(CE_Failure, CPLE_NotSupported,
3637
0
                         "Only overviews %s can be computed",
3638
0
                         osOvrList.c_str());
3639
0
                return CE_Failure;
3640
0
            }
3641
0
            else
3642
0
            {
3643
0
                int nOvFactor = panOverviewList[i];
3644
0
                if (jCandidate < 0)
3645
0
                    jCandidate = static_cast<int>(m_apoOverviewDS.size());
3646
3647
0
                int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3648
0
                int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3649
0
                if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3650
0
                      nOvFactor == 2 * nMaxOvFactor) &&
3651
0
                    !m_bZoomOther)
3652
0
                {
3653
0
                    CPLError(CE_Warning, CPLE_AppDefined,
3654
0
                             "Use of overview factor %d causes gpkg_zoom_other "
3655
0
                             "extension to be needed",
3656
0
                             nOvFactor);
3657
0
                    RegisterZoomOtherExtension();
3658
0
                    m_bZoomOther = true;
3659
0
                }
3660
3661
0
                SoftStartTransaction();
3662
3663
0
                CPLAssert(jCandidate > 0);
3664
0
                const int nNewZoomLevel =
3665
0
                    m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3666
3667
0
                char *pszSQL;
3668
0
                OGRErr eErr;
3669
0
                for (int k = 0; k <= jCandidate; k++)
3670
0
                {
3671
0
                    pszSQL = sqlite3_mprintf(
3672
0
                        "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3673
0
                        "WHERE lower(table_name) = lower('%q') AND zoom_level "
3674
0
                        "= %d",
3675
0
                        m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3676
0
                        m_nZoomLevel - k);
3677
0
                    eErr = SQLCommand(hDB, pszSQL);
3678
0
                    sqlite3_free(pszSQL);
3679
0
                    if (eErr != OGRERR_NONE)
3680
0
                    {
3681
0
                        SoftRollbackTransaction();
3682
0
                        return CE_Failure;
3683
0
                    }
3684
3685
0
                    pszSQL =
3686
0
                        sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3687
0
                                        "WHERE zoom_level = %d",
3688
0
                                        m_osRasterTable.c_str(),
3689
0
                                        m_nZoomLevel - k + 1, m_nZoomLevel - k);
3690
0
                    eErr = SQLCommand(hDB, pszSQL);
3691
0
                    sqlite3_free(pszSQL);
3692
0
                    if (eErr != OGRERR_NONE)
3693
0
                    {
3694
0
                        SoftRollbackTransaction();
3695
0
                        return CE_Failure;
3696
0
                    }
3697
0
                }
3698
3699
0
                double dfGDALMinX = m_adfGeoTransform[0];
3700
0
                double dfGDALMinY =
3701
0
                    m_adfGeoTransform[3] + nRasterYSize * m_adfGeoTransform[5];
3702
0
                double dfGDALMaxX =
3703
0
                    m_adfGeoTransform[0] + nRasterXSize * m_adfGeoTransform[1];
3704
0
                double dfGDALMaxY = m_adfGeoTransform[3];
3705
0
                double dfPixelXSizeZoomLevel = m_adfGeoTransform[1] * nOvFactor;
3706
0
                double dfPixelYSizeZoomLevel =
3707
0
                    fabs(m_adfGeoTransform[5]) * nOvFactor;
3708
0
                int nTileWidth, nTileHeight;
3709
0
                GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3710
0
                int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
3711
0
                int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
3712
0
                pszSQL = sqlite3_mprintf(
3713
0
                    "INSERT INTO gpkg_tile_matrix "
3714
0
                    "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3715
0
                    "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3716
0
                    "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3717
0
                    m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3718
0
                    nTileMatrixHeight, nTileWidth, nTileHeight,
3719
0
                    dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3720
0
                eErr = SQLCommand(hDB, pszSQL);
3721
0
                sqlite3_free(pszSQL);
3722
0
                if (eErr != OGRERR_NONE)
3723
0
                {
3724
0
                    SoftRollbackTransaction();
3725
0
                    return CE_Failure;
3726
0
                }
3727
3728
0
                SoftCommitTransaction();
3729
3730
0
                m_nZoomLevel++; /* this change our zoom level as well as
3731
                                   previous overviews */
3732
0
                for (int k = 0; k < jCandidate; k++)
3733
0
                    m_apoOverviewDS[k]->m_nZoomLevel++;
3734
3735
0
                auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3736
0
                poOvrDS->ShareLockWithParentDataset(this);
3737
0
                poOvrDS->InitRaster(
3738
0
                    this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3739
0
                    m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3740
0
                    nTileWidth, nTileHeight, nTileMatrixWidth,
3741
0
                    nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3742
0
                    dfGDALMaxY);
3743
0
                m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3744
0
                                       std::move(poOvrDS));
3745
0
            }
3746
0
        }
3747
0
    }
3748
3749
0
    GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3750
0
        CPLCalloc(sizeof(GDALRasterBand **), nBands));
3751
0
    CPLErr eErr = CE_None;
3752
0
    for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3753
0
    {
3754
0
        papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3755
0
            CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3756
0
        int iCurOverview = 0;
3757
0
        for (int i = 0; i < nOverviews; i++)
3758
0
        {
3759
0
            bool bFound = false;
3760
0
            for (const auto &poODS : m_apoOverviewDS)
3761
0
            {
3762
0
                const int nOvFactor = static_cast<int>(
3763
0
                    0.5 + poODS->m_adfGeoTransform[1] / m_adfGeoTransform[1]);
3764
3765
0
                if (nOvFactor == panOverviewList[i])
3766
0
                {
3767
0
                    papapoOverviewBands[iBand][iCurOverview] =
3768
0
                        poODS->GetRasterBand(iBand + 1);
3769
0
                    iCurOverview++;
3770
0
                    bFound = true;
3771
0
                    break;
3772
0
                }
3773
0
            }
3774
0
            if (!bFound)
3775
0
            {
3776
0
                CPLError(CE_Failure, CPLE_AppDefined,
3777
0
                         "Could not find dataset corresponding to ov factor %d",
3778
0
                         panOverviewList[i]);
3779
0
                eErr = CE_Failure;
3780
0
            }
3781
0
        }
3782
0
        if (eErr == CE_None)
3783
0
        {
3784
0
            CPLAssert(iCurOverview == nOverviews);
3785
0
        }
3786
0
    }
3787
3788
0
    if (eErr == CE_None)
3789
0
        eErr = GDALRegenerateOverviewsMultiBand(
3790
0
            nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3791
0
            pfnProgress, pProgressData, papszOptions);
3792
3793
0
    for (int iBand = 0; iBand < nBands; iBand++)
3794
0
    {
3795
0
        CPLFree(papapoOverviewBands[iBand]);
3796
0
    }
3797
0
    CPLFree(papapoOverviewBands);
3798
3799
0
    return eErr;
3800
0
}
3801
3802
/************************************************************************/
3803
/*                            GetFileList()                             */
3804
/************************************************************************/
3805
3806
char **GDALGeoPackageDataset::GetFileList()
3807
26
{
3808
26
    TryLoadXML();
3809
26
    return GDALPamDataset::GetFileList();
3810
26
}
3811
3812
/************************************************************************/
3813
/*                      GetMetadataDomainList()                         */
3814
/************************************************************************/
3815
3816
char **GDALGeoPackageDataset::GetMetadataDomainList()
3817
0
{
3818
0
    GetMetadata();
3819
0
    if (!m_osRasterTable.empty())
3820
0
        GetMetadata("GEOPACKAGE");
3821
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3822
0
                                   TRUE, "SUBDATASETS", nullptr);
3823
0
}
3824
3825
/************************************************************************/
3826
/*                        CheckMetadataDomain()                         */
3827
/************************************************************************/
3828
3829
const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3830
5.67k
{
3831
5.67k
    if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3832
5.67k
        m_osRasterTable.empty())
3833
0
    {
3834
0
        CPLError(
3835
0
            CE_Warning, CPLE_IllegalArg,
3836
0
            "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3837
0
            "Using default domain instead");
3838
0
        return nullptr;
3839
0
    }
3840
5.67k
    return pszDomain;
3841
5.67k
}
3842
3843
/************************************************************************/
3844
/*                           HasMetadataTables()                        */
3845
/************************************************************************/
3846
3847
bool GDALGeoPackageDataset::HasMetadataTables() const
3848
5.17k
{
3849
5.17k
    if (m_nHasMetadataTables < 0)
3850
2.27k
    {
3851
2.27k
        const int nCount =
3852
2.27k
            SQLGetInteger(hDB,
3853
2.27k
                          "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3854
2.27k
                          "('gpkg_metadata', 'gpkg_metadata_reference') "
3855
2.27k
                          "AND type IN ('table', 'view')",
3856
2.27k
                          nullptr);
3857
2.27k
        m_nHasMetadataTables = nCount == 2;
3858
2.27k
    }
3859
5.17k
    return CPL_TO_BOOL(m_nHasMetadataTables);
3860
5.17k
}
3861
3862
/************************************************************************/
3863
/*                         HasDataColumnsTable()                        */
3864
/************************************************************************/
3865
3866
bool GDALGeoPackageDataset::HasDataColumnsTable() const
3867
1.24k
{
3868
1.24k
    const int nCount = SQLGetInteger(
3869
1.24k
        hDB,
3870
1.24k
        "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3871
1.24k
        "AND type IN ('table', 'view')",
3872
1.24k
        nullptr);
3873
1.24k
    return nCount == 1;
3874
1.24k
}
3875
3876
/************************************************************************/
3877
/*                    HasDataColumnConstraintsTable()                   */
3878
/************************************************************************/
3879
3880
bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3881
0
{
3882
0
    const int nCount = SQLGetInteger(hDB,
3883
0
                                     "SELECT 1 FROM sqlite_master WHERE name = "
3884
0
                                     "'gpkg_data_column_constraints'"
3885
0
                                     "AND type IN ('table', 'view')",
3886
0
                                     nullptr);
3887
0
    return nCount == 1;
3888
0
}
3889
3890
/************************************************************************/
3891
/*                  HasDataColumnConstraintsTableGPKG_1_0()             */
3892
/************************************************************************/
3893
3894
bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3895
0
{
3896
0
    if (m_nApplicationId != GP10_APPLICATION_ID)
3897
0
        return false;
3898
    // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3899
    // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3900
0
    bool bRet = false;
3901
0
    sqlite3_stmt *hSQLStmt = nullptr;
3902
0
    int rc = sqlite3_prepare_v2(hDB,
3903
0
                                "SELECT minIsInclusive, maxIsInclusive FROM "
3904
0
                                "gpkg_data_column_constraints",
3905
0
                                -1, &hSQLStmt, nullptr);
3906
0
    if (rc == SQLITE_OK)
3907
0
    {
3908
0
        bRet = true;
3909
0
        sqlite3_finalize(hSQLStmt);
3910
0
    }
3911
0
    return bRet;
3912
0
}
3913
3914
/************************************************************************/
3915
/*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
3916
/************************************************************************/
3917
3918
bool GDALGeoPackageDataset::
3919
    CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3920
0
{
3921
0
    if (!HasDataColumnsTable())
3922
0
    {
3923
        // Geopackage < 1.3 had
3924
        // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3925
        // gpkg_contents(table_name) instead of the unique constraint.
3926
0
        if (OGRERR_NONE !=
3927
0
            SQLCommand(
3928
0
                GetDB(),
3929
0
                "CREATE TABLE gpkg_data_columns ("
3930
0
                "table_name TEXT NOT NULL,"
3931
0
                "column_name TEXT NOT NULL,"
3932
0
                "name TEXT,"
3933
0
                "title TEXT,"
3934
0
                "description TEXT,"
3935
0
                "mime_type TEXT,"
3936
0
                "constraint_name TEXT,"
3937
0
                "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3938
0
                "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3939
0
        {
3940
0
            return false;
3941
0
        }
3942
0
    }
3943
0
    if (!HasDataColumnConstraintsTable())
3944
0
    {
3945
0
        const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3946
0
                                           ? "min_is_inclusive"
3947
0
                                           : "minIsInclusive";
3948
0
        const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3949
0
                                           ? "max_is_inclusive"
3950
0
                                           : "maxIsInclusive";
3951
3952
0
        const std::string osSQL(
3953
0
            CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3954
0
                       "constraint_name TEXT NOT NULL,"
3955
0
                       "constraint_type TEXT NOT NULL,"
3956
0
                       "value TEXT,"
3957
0
                       "min NUMERIC,"
3958
0
                       "%s BOOLEAN,"
3959
0
                       "max NUMERIC,"
3960
0
                       "%s BOOLEAN,"
3961
0
                       "description TEXT,"
3962
0
                       "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3963
0
                       "constraint_type, value));",
3964
0
                       min_is_inclusive, max_is_inclusive));
3965
0
        if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3966
0
        {
3967
0
            return false;
3968
0
        }
3969
0
    }
3970
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3971
0
    {
3972
0
        return false;
3973
0
    }
3974
0
    if (SQLGetInteger(GetDB(),
3975
0
                      "SELECT 1 FROM gpkg_extensions WHERE "
3976
0
                      "table_name = 'gpkg_data_columns'",
3977
0
                      nullptr) != 1)
3978
0
    {
3979
0
        if (OGRERR_NONE !=
3980
0
            SQLCommand(
3981
0
                GetDB(),
3982
0
                "INSERT INTO gpkg_extensions "
3983
0
                "(table_name,column_name,extension_name,definition,scope) "
3984
0
                "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3985
0
                "'http://www.geopackage.org/spec121/#extension_schema', "
3986
0
                "'read-write')"))
3987
0
        {
3988
0
            return false;
3989
0
        }
3990
0
    }
3991
0
    if (SQLGetInteger(GetDB(),
3992
0
                      "SELECT 1 FROM gpkg_extensions WHERE "
3993
0
                      "table_name = 'gpkg_data_column_constraints'",
3994
0
                      nullptr) != 1)
3995
0
    {
3996
0
        if (OGRERR_NONE !=
3997
0
            SQLCommand(
3998
0
                GetDB(),
3999
0
                "INSERT INTO gpkg_extensions "
4000
0
                "(table_name,column_name,extension_name,definition,scope) "
4001
0
                "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4002
0
                "'http://www.geopackage.org/spec121/#extension_schema', "
4003
0
                "'read-write')"))
4004
0
        {
4005
0
            return false;
4006
0
        }
4007
0
    }
4008
4009
0
    return true;
4010
0
}
4011
4012
/************************************************************************/
4013
/*                        HasGpkgextRelationsTable()                    */
4014
/************************************************************************/
4015
4016
bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4017
419
{
4018
419
    const int nCount = SQLGetInteger(
4019
419
        hDB,
4020
419
        "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4021
419
        "AND type IN ('table', 'view')",
4022
419
        nullptr);
4023
419
    return nCount == 1;
4024
419
}
4025
4026
/************************************************************************/
4027
/*                    CreateRelationsTableIfNecessary()                 */
4028
/************************************************************************/
4029
4030
bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4031
0
{
4032
0
    if (HasGpkgextRelationsTable())
4033
0
    {
4034
0
        return true;
4035
0
    }
4036
4037
0
    if (OGRERR_NONE !=
4038
0
        SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4039
0
                            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4040
0
                            "base_table_name TEXT NOT NULL,"
4041
0
                            "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4042
0
                            "related_table_name TEXT NOT NULL,"
4043
0
                            "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4044
0
                            "relation_name TEXT NOT NULL,"
4045
0
                            "mapping_table_name TEXT NOT NULL UNIQUE);"))
4046
0
    {
4047
0
        return false;
4048
0
    }
4049
4050
0
    return true;
4051
0
}
4052
4053
/************************************************************************/
4054
/*                        HasQGISLayerStyles()                          */
4055
/************************************************************************/
4056
4057
bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4058
0
{
4059
    // QGIS layer_styles extension:
4060
    // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4061
0
    bool bRet = false;
4062
0
    const int nCount =
4063
0
        SQLGetInteger(hDB,
4064
0
                      "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4065
0
                      "AND type = 'table'",
4066
0
                      nullptr);
4067
0
    if (nCount == 1)
4068
0
    {
4069
0
        sqlite3_stmt *hSQLStmt = nullptr;
4070
0
        int rc = sqlite3_prepare_v2(
4071
0
            hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4072
0
            &hSQLStmt, nullptr);
4073
0
        if (rc == SQLITE_OK)
4074
0
        {
4075
0
            bRet = true;
4076
0
            sqlite3_finalize(hSQLStmt);
4077
0
        }
4078
0
    }
4079
0
    return bRet;
4080
0
}
4081
4082
/************************************************************************/
4083
/*                            GetMetadata()                             */
4084
/************************************************************************/
4085
4086
char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4087
4088
3.85k
{
4089
3.85k
    pszDomain = CheckMetadataDomain(pszDomain);
4090
3.85k
    if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4091
0
        return m_aosSubDatasets.List();
4092
4093
3.85k
    if (m_bHasReadMetadataFromStorage)
4094
1.58k
        return GDALPamDataset::GetMetadata(pszDomain);
4095
4096
2.27k
    m_bHasReadMetadataFromStorage = true;
4097
4098
2.27k
    TryLoadXML();
4099
4100
2.27k
    if (!HasMetadataTables())
4101
2.27k
        return GDALPamDataset::GetMetadata(pszDomain);
4102
4103
0
    char *pszSQL = nullptr;
4104
0
    if (!m_osRasterTable.empty())
4105
0
    {
4106
0
        pszSQL = sqlite3_mprintf(
4107
0
            "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4108
0
            "mdr.reference_scope FROM gpkg_metadata md "
4109
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4110
0
            "WHERE "
4111
0
            "(mdr.reference_scope = 'geopackage' OR "
4112
0
            "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4113
0
            "lower('%q'))) ORDER BY md.id "
4114
0
            "LIMIT 1000",  // to avoid denial of service
4115
0
            m_osRasterTable.c_str());
4116
0
    }
4117
0
    else
4118
0
    {
4119
0
        pszSQL = sqlite3_mprintf(
4120
0
            "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4121
0
            "mdr.reference_scope FROM gpkg_metadata md "
4122
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4123
0
            "WHERE "
4124
0
            "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4125
0
            "LIMIT 1000"  // to avoid denial of service
4126
0
        );
4127
0
    }
4128
4129
0
    auto oResult = SQLQuery(hDB, pszSQL);
4130
0
    sqlite3_free(pszSQL);
4131
0
    if (!oResult)
4132
0
    {
4133
0
        return GDALPamDataset::GetMetadata(pszDomain);
4134
0
    }
4135
4136
0
    char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4137
4138
    /* GDAL metadata */
4139
0
    for (int i = 0; i < oResult->RowCount(); i++)
4140
0
    {
4141
0
        const char *pszMetadata = oResult->GetValue(0, i);
4142
0
        const char *pszMDStandardURI = oResult->GetValue(1, i);
4143
0
        const char *pszMimeType = oResult->GetValue(2, i);
4144
0
        const char *pszReferenceScope = oResult->GetValue(3, i);
4145
0
        if (pszMetadata && pszMDStandardURI && pszMimeType &&
4146
0
            pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4147
0
            EQUAL(pszMimeType, "text/xml"))
4148
0
        {
4149
0
            CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4150
0
            if (psXMLNode)
4151
0
            {
4152
0
                GDALMultiDomainMetadata oLocalMDMD;
4153
0
                oLocalMDMD.XMLInit(psXMLNode, FALSE);
4154
0
                if (!m_osRasterTable.empty() &&
4155
0
                    EQUAL(pszReferenceScope, "geopackage"))
4156
0
                {
4157
0
                    oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4158
0
                }
4159
0
                else
4160
0
                {
4161
0
                    papszMetadata =
4162
0
                        CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4163
0
                    CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4164
0
                    CSLConstList papszIter = papszDomainList;
4165
0
                    while (papszIter && *papszIter)
4166
0
                    {
4167
0
                        if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4168
0
                        {
4169
0
                            CSLConstList papszMD =
4170
0
                                oLocalMDMD.GetMetadata(*papszIter);
4171
0
                            const char *pszBAND_COUNT =
4172
0
                                CSLFetchNameValue(papszMD, "BAND_COUNT");
4173
0
                            if (pszBAND_COUNT)
4174
0
                                m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4175
4176
0
                            const char *pszCOLOR_TABLE =
4177
0
                                CSLFetchNameValue(papszMD, "COLOR_TABLE");
4178
0
                            if (pszCOLOR_TABLE)
4179
0
                            {
4180
0
                                const CPLStringList aosTokens(
4181
0
                                    CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4182
0
                                                       0));
4183
0
                                if ((aosTokens.size() % 4) == 0)
4184
0
                                {
4185
0
                                    const int nColors = aosTokens.size() / 4;
4186
0
                                    m_poCTFromMetadata =
4187
0
                                        std::make_unique<GDALColorTable>();
4188
0
                                    for (int iColor = 0; iColor < nColors;
4189
0
                                         ++iColor)
4190
0
                                    {
4191
0
                                        GDALColorEntry sEntry;
4192
0
                                        sEntry.c1 = static_cast<short>(
4193
0
                                            atoi(aosTokens[4 * iColor + 0]));
4194
0
                                        sEntry.c2 = static_cast<short>(
4195
0
                                            atoi(aosTokens[4 * iColor + 1]));
4196
0
                                        sEntry.c3 = static_cast<short>(
4197
0
                                            atoi(aosTokens[4 * iColor + 2]));
4198
0
                                        sEntry.c4 = static_cast<short>(
4199
0
                                            atoi(aosTokens[4 * iColor + 3]));
4200
0
                                        m_poCTFromMetadata->SetColorEntry(
4201
0
                                            iColor, &sEntry);
4202
0
                                    }
4203
0
                                }
4204
0
                            }
4205
4206
0
                            const char *pszTILE_FORMAT =
4207
0
                                CSLFetchNameValue(papszMD, "TILE_FORMAT");
4208
0
                            if (pszTILE_FORMAT)
4209
0
                            {
4210
0
                                m_osTFFromMetadata = pszTILE_FORMAT;
4211
0
                                oMDMD.SetMetadataItem("TILE_FORMAT",
4212
0
                                                      pszTILE_FORMAT,
4213
0
                                                      "IMAGE_STRUCTURE");
4214
0
                            }
4215
4216
0
                            const char *pszNodataValue =
4217
0
                                CSLFetchNameValue(papszMD, "NODATA_VALUE");
4218
0
                            if (pszNodataValue)
4219
0
                            {
4220
0
                                m_osNodataValueFromMetadata = pszNodataValue;
4221
0
                            }
4222
0
                        }
4223
4224
0
                        else if (!EQUAL(*papszIter, "") &&
4225
0
                                 !STARTS_WITH(*papszIter, "BAND_"))
4226
0
                        {
4227
0
                            oMDMD.SetMetadata(
4228
0
                                oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4229
0
                        }
4230
0
                        papszIter++;
4231
0
                    }
4232
0
                }
4233
0
                CPLDestroyXMLNode(psXMLNode);
4234
0
            }
4235
0
        }
4236
0
    }
4237
4238
0
    GDALPamDataset::SetMetadata(papszMetadata);
4239
0
    CSLDestroy(papszMetadata);
4240
0
    papszMetadata = nullptr;
4241
4242
    /* Add non-GDAL metadata now */
4243
0
    int nNonGDALMDILocal = 1;
4244
0
    int nNonGDALMDIGeopackage = 1;
4245
0
    for (int i = 0; i < oResult->RowCount(); i++)
4246
0
    {
4247
0
        const char *pszMetadata = oResult->GetValue(0, i);
4248
0
        const char *pszMDStandardURI = oResult->GetValue(1, i);
4249
0
        const char *pszMimeType = oResult->GetValue(2, i);
4250
0
        const char *pszReferenceScope = oResult->GetValue(3, i);
4251
0
        if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4252
0
            pszMimeType == nullptr || pszReferenceScope == nullptr)
4253
0
        {
4254
            // should not happen as there are NOT NULL constraints
4255
            // But a database could lack such NOT NULL constraints or have
4256
            // large values that would cause a memory allocation failure.
4257
0
            continue;
4258
0
        }
4259
0
        int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4260
0
        if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4261
0
            EQUAL(pszMimeType, "text/xml"))
4262
0
            continue;
4263
4264
0
        if (!m_osRasterTable.empty() && bIsGPKGScope)
4265
0
        {
4266
0
            oMDMD.SetMetadataItem(
4267
0
                CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4268
0
                pszMetadata, "GEOPACKAGE");
4269
0
            nNonGDALMDIGeopackage++;
4270
0
        }
4271
        /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4272
        ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4273
        {
4274
            char* apszMD[2];
4275
            apszMD[0] = (char*)pszMetadata;
4276
            apszMD[1] = NULL;
4277
            oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4278
        }*/
4279
0
        else
4280
0
        {
4281
0
            oMDMD.SetMetadataItem(
4282
0
                CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4283
0
                pszMetadata);
4284
0
            nNonGDALMDILocal++;
4285
0
        }
4286
0
    }
4287
4288
0
    return GDALPamDataset::GetMetadata(pszDomain);
4289
0
}
4290
4291
/************************************************************************/
4292
/*                            WriteMetadata()                           */
4293
/************************************************************************/
4294
4295
void GDALGeoPackageDataset::WriteMetadata(
4296
    CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4297
    const char *pszTableName)
4298
0
{
4299
0
    const bool bIsEmpty = (psXMLNode == nullptr);
4300
0
    if (!HasMetadataTables())
4301
0
    {
4302
0
        if (bIsEmpty || !CreateMetadataTables())
4303
0
        {
4304
0
            CPLDestroyXMLNode(psXMLNode);
4305
0
            return;
4306
0
        }
4307
0
    }
4308
4309
0
    char *pszXML = nullptr;
4310
0
    if (!bIsEmpty)
4311
0
    {
4312
0
        CPLXMLNode *psMasterXMLNode =
4313
0
            CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4314
0
        psMasterXMLNode->psChild = psXMLNode;
4315
0
        pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4316
0
        CPLDestroyXMLNode(psMasterXMLNode);
4317
0
    }
4318
    // cppcheck-suppress uselessAssignmentPtrArg
4319
0
    psXMLNode = nullptr;
4320
4321
0
    char *pszSQL = nullptr;
4322
0
    if (pszTableName && pszTableName[0] != '\0')
4323
0
    {
4324
0
        pszSQL = sqlite3_mprintf(
4325
0
            "SELECT md.id FROM gpkg_metadata md "
4326
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4327
0
            "WHERE md.md_scope = 'dataset' AND "
4328
0
            "md.md_standard_uri='http://gdal.org' "
4329
0
            "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4330
0
            "lower(mdr.table_name) = lower('%q')",
4331
0
            pszTableName);
4332
0
    }
4333
0
    else
4334
0
    {
4335
0
        pszSQL = sqlite3_mprintf(
4336
0
            "SELECT md.id FROM gpkg_metadata md "
4337
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4338
0
            "WHERE md.md_scope = 'dataset' AND "
4339
0
            "md.md_standard_uri='http://gdal.org' "
4340
0
            "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4341
0
            "'geopackage'");
4342
0
    }
4343
0
    OGRErr err;
4344
0
    int mdId = SQLGetInteger(hDB, pszSQL, &err);
4345
0
    if (err != OGRERR_NONE)
4346
0
        mdId = -1;
4347
0
    sqlite3_free(pszSQL);
4348
4349
0
    if (bIsEmpty)
4350
0
    {
4351
0
        if (mdId >= 0)
4352
0
        {
4353
0
            SQLCommand(
4354
0
                hDB,
4355
0
                CPLSPrintf(
4356
0
                    "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4357
0
                    mdId));
4358
0
            SQLCommand(
4359
0
                hDB,
4360
0
                CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4361
0
        }
4362
0
    }
4363
0
    else
4364
0
    {
4365
0
        if (mdId >= 0)
4366
0
        {
4367
0
            pszSQL = sqlite3_mprintf(
4368
0
                "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4369
0
                pszXML, mdId);
4370
0
        }
4371
0
        else
4372
0
        {
4373
0
            pszSQL =
4374
0
                sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4375
0
                                "md_standard_uri, mime_type, metadata) VALUES "
4376
0
                                "('dataset','http://gdal.org','text/xml','%q')",
4377
0
                                pszXML);
4378
0
        }
4379
0
        SQLCommand(hDB, pszSQL);
4380
0
        sqlite3_free(pszSQL);
4381
4382
0
        CPLFree(pszXML);
4383
4384
0
        if (mdId < 0)
4385
0
        {
4386
0
            const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4387
0
            if (pszTableName != nullptr && pszTableName[0] != '\0')
4388
0
            {
4389
0
                pszSQL = sqlite3_mprintf(
4390
0
                    "INSERT INTO gpkg_metadata_reference (reference_scope, "
4391
0
                    "table_name, timestamp, md_file_id) VALUES "
4392
0
                    "('table', '%q', %s, %d)",
4393
0
                    pszTableName, GetCurrentDateEscapedSQL().c_str(),
4394
0
                    static_cast<int>(nFID));
4395
0
            }
4396
0
            else
4397
0
            {
4398
0
                pszSQL = sqlite3_mprintf(
4399
0
                    "INSERT INTO gpkg_metadata_reference (reference_scope, "
4400
0
                    "timestamp, md_file_id) VALUES "
4401
0
                    "('geopackage', %s, %d)",
4402
0
                    GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4403
0
            }
4404
0
        }
4405
0
        else
4406
0
        {
4407
0
            pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4408
0
                                     "timestamp = %s WHERE md_file_id = %d",
4409
0
                                     GetCurrentDateEscapedSQL().c_str(), mdId);
4410
0
        }
4411
0
        SQLCommand(hDB, pszSQL);
4412
0
        sqlite3_free(pszSQL);
4413
0
    }
4414
0
}
4415
4416
/************************************************************************/
4417
/*                        CreateMetadataTables()                        */
4418
/************************************************************************/
4419
4420
bool GDALGeoPackageDataset::CreateMetadataTables()
4421
0
{
4422
0
    const bool bCreateTriggers =
4423
0
        CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4424
4425
    /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
4426
0
    CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4427
0
                      "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4428
0
                      "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4429
0
                      "md_standard_uri TEXT NOT NULL,"
4430
0
                      "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4431
0
                      "metadata TEXT NOT NULL DEFAULT ''"
4432
0
                      ")";
4433
4434
    /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
4435
0
    const char *pszMetadataTriggers =
4436
0
        "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4437
0
        "BEFORE INSERT ON 'gpkg_metadata' "
4438
0
        "FOR EACH ROW BEGIN "
4439
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4440
0
        "constraint: md_scope must be one of undefined | fieldSession | "
4441
0
        "collectionSession | series | dataset | featureType | feature | "
4442
0
        "attributeType | attribute | tile | model | catalogue | schema | "
4443
0
        "taxonomy software | service | collectionHardware | "
4444
0
        "nonGeographicDataset | dimensionGroup') "
4445
0
        "WHERE NOT(NEW.md_scope IN "
4446
0
        "('undefined','fieldSession','collectionSession','series','dataset', "
4447
0
        "'featureType','feature','attributeType','attribute','tile','model', "
4448
0
        "'catalogue','schema','taxonomy','software','service', "
4449
0
        "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4450
0
        "END; "
4451
0
        "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4452
0
        "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4453
0
        "FOR EACH ROW BEGIN "
4454
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4455
0
        "constraint: md_scope must be one of undefined | fieldSession | "
4456
0
        "collectionSession | series | dataset | featureType | feature | "
4457
0
        "attributeType | attribute | tile | model | catalogue | schema | "
4458
0
        "taxonomy software | service | collectionHardware | "
4459
0
        "nonGeographicDataset | dimensionGroup') "
4460
0
        "WHERE NOT(NEW.md_scope IN "
4461
0
        "('undefined','fieldSession','collectionSession','series','dataset', "
4462
0
        "'featureType','feature','attributeType','attribute','tile','model', "
4463
0
        "'catalogue','schema','taxonomy','software','service', "
4464
0
        "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4465
0
        "END";
4466
0
    if (bCreateTriggers)
4467
0
    {
4468
0
        osSQL += ";";
4469
0
        osSQL += pszMetadataTriggers;
4470
0
    }
4471
4472
    /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4473
     * Table Definition SQL */
4474
0
    osSQL += ";"
4475
0
             "CREATE TABLE gpkg_metadata_reference ("
4476
0
             "reference_scope TEXT NOT NULL,"
4477
0
             "table_name TEXT,"
4478
0
             "column_name TEXT,"
4479
0
             "row_id_value INTEGER,"
4480
0
             "timestamp DATETIME NOT NULL DEFAULT "
4481
0
             "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4482
0
             "md_file_id INTEGER NOT NULL,"
4483
0
             "md_parent_id INTEGER,"
4484
0
             "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4485
0
             "gpkg_metadata(id),"
4486
0
             "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4487
0
             "gpkg_metadata(id)"
4488
0
             ")";
4489
4490
    /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4491
     * Definition SQL   */
4492
0
    const char *pszMetadataReferenceTriggers =
4493
0
        "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4494
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4495
0
        "FOR EACH ROW BEGIN "
4496
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4497
0
        "violates constraint: reference_scope must be one of \"geopackage\", "
4498
0
        "table\", \"column\", \"row\", \"row/col\"') "
4499
0
        "WHERE NOT NEW.reference_scope IN "
4500
0
        "('geopackage','table','column','row','row/col'); "
4501
0
        "END; "
4502
0
        "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4503
0
        "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4504
0
        "FOR EACH ROW BEGIN "
4505
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4506
0
        "violates constraint: reference_scope must be one of \"geopackage\", "
4507
0
        "\"table\", \"column\", \"row\", \"row/col\"') "
4508
0
        "WHERE NOT NEW.reference_scope IN "
4509
0
        "('geopackage','table','column','row','row/col'); "
4510
0
        "END; "
4511
0
        "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4512
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4513
0
        "FOR EACH ROW BEGIN "
4514
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4515
0
        "violates constraint: column name must be NULL when reference_scope "
4516
0
        "is \"geopackage\", \"table\" or \"row\"') "
4517
0
        "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4518
0
        "AND NEW.column_name IS NOT NULL); "
4519
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4520
0
        "violates constraint: column name must be defined for the specified "
4521
0
        "table when reference_scope is \"column\" or \"row/col\"') "
4522
0
        "WHERE (NEW.reference_scope IN ('column','row/col') "
4523
0
        "AND NOT NEW.table_name IN ( "
4524
0
        "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4525
0
        "AND name = NEW.table_name "
4526
0
        "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4527
0
        "END; "
4528
0
        "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4529
0
        "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4530
0
        "FOR EACH ROW BEGIN "
4531
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4532
0
        "violates constraint: column name must be NULL when reference_scope "
4533
0
        "is \"geopackage\", \"table\" or \"row\"') "
4534
0
        "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4535
0
        "AND NEW.column_name IS NOT NULL); "
4536
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4537
0
        "violates constraint: column name must be defined for the specified "
4538
0
        "table when reference_scope is \"column\" or \"row/col\"') "
4539
0
        "WHERE (NEW.reference_scope IN ('column','row/col') "
4540
0
        "AND NOT NEW.table_name IN ( "
4541
0
        "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4542
0
        "AND name = NEW.table_name "
4543
0
        "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4544
0
        "END; "
4545
0
        "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4546
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4547
0
        "FOR EACH ROW BEGIN "
4548
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4549
0
        "violates constraint: row_id_value must be NULL when reference_scope "
4550
0
        "is \"geopackage\", \"table\" or \"column\"') "
4551
0
        "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4552
0
        "AND NEW.row_id_value IS NOT NULL; "
4553
0
        "END; "
4554
0
        "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4555
0
        "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4556
0
        "FOR EACH ROW BEGIN "
4557
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4558
0
        "violates constraint: row_id_value must be NULL when reference_scope "
4559
0
        "is \"geopackage\", \"table\" or \"column\"') "
4560
0
        "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4561
0
        "AND NEW.row_id_value IS NOT NULL; "
4562
0
        "END; "
4563
0
        "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4564
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4565
0
        "FOR EACH ROW BEGIN "
4566
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4567
0
        "violates constraint: timestamp must be a valid time in ISO 8601 "
4568
0
        "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4569
0
        "WHERE NOT (NEW.timestamp GLOB "
4570
0
        "'[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-"
4571
0
        "5][0-9].[0-9][0-9][0-9]Z' "
4572
0
        "AND strftime('%s',NEW.timestamp) NOT NULL); "
4573
0
        "END; "
4574
0
        "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4575
0
        "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4576
0
        "FOR EACH ROW BEGIN "
4577
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4578
0
        "violates constraint: timestamp must be a valid time in ISO 8601 "
4579
0
        "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4580
0
        "WHERE NOT (NEW.timestamp GLOB "
4581
0
        "'[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-"
4582
0
        "5][0-9].[0-9][0-9][0-9]Z' "
4583
0
        "AND strftime('%s',NEW.timestamp) NOT NULL); "
4584
0
        "END";
4585
0
    if (bCreateTriggers)
4586
0
    {
4587
0
        osSQL += ";";
4588
0
        osSQL += pszMetadataReferenceTriggers;
4589
0
    }
4590
4591
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4592
0
        return false;
4593
4594
0
    osSQL += ";";
4595
0
    osSQL += "INSERT INTO gpkg_extensions "
4596
0
             "(table_name, column_name, extension_name, definition, scope) "
4597
0
             "VALUES "
4598
0
             "('gpkg_metadata', NULL, 'gpkg_metadata', "
4599
0
             "'http://www.geopackage.org/spec120/#extension_metadata', "
4600
0
             "'read-write')";
4601
4602
0
    osSQL += ";";
4603
0
    osSQL += "INSERT INTO gpkg_extensions "
4604
0
             "(table_name, column_name, extension_name, definition, scope) "
4605
0
             "VALUES "
4606
0
             "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4607
0
             "'http://www.geopackage.org/spec120/#extension_metadata', "
4608
0
             "'read-write')";
4609
4610
0
    const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4611
0
    m_nHasMetadataTables = bOK;
4612
0
    return bOK;
4613
0
}
4614
4615
/************************************************************************/
4616
/*                            FlushMetadata()                           */
4617
/************************************************************************/
4618
4619
void GDALGeoPackageDataset::FlushMetadata()
4620
123k
{
4621
123k
    if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4622
123k
        m_nCreateMetadataTables == FALSE)
4623
123k
        return;
4624
0
    m_bMetadataDirty = false;
4625
4626
0
    if (eAccess == GA_ReadOnly)
4627
0
    {
4628
0
        return;
4629
0
    }
4630
4631
0
    bool bCanWriteAreaOrPoint =
4632
0
        !m_bGridCellEncodingAsCO &&
4633
0
        (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4634
0
    if (!m_osRasterTable.empty())
4635
0
    {
4636
0
        const char *pszIdentifier =
4637
0
            GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4638
0
        const char *pszDescription =
4639
0
            GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4640
0
        if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4641
0
            pszIdentifier != m_osIdentifier)
4642
0
        {
4643
0
            m_osIdentifier = pszIdentifier;
4644
0
            char *pszSQL =
4645
0
                sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4646
0
                                "WHERE lower(table_name) = lower('%q')",
4647
0
                                pszIdentifier, m_osRasterTable.c_str());
4648
0
            SQLCommand(hDB, pszSQL);
4649
0
            sqlite3_free(pszSQL);
4650
0
        }
4651
0
        if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4652
0
            pszDescription != m_osDescription)
4653
0
        {
4654
0
            m_osDescription = pszDescription;
4655
0
            char *pszSQL =
4656
0
                sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4657
0
                                "WHERE lower(table_name) = lower('%q')",
4658
0
                                pszDescription, m_osRasterTable.c_str());
4659
0
            SQLCommand(hDB, pszSQL);
4660
0
            sqlite3_free(pszSQL);
4661
0
        }
4662
0
        if (bCanWriteAreaOrPoint)
4663
0
        {
4664
0
            const char *pszAreaOrPoint =
4665
0
                GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4666
0
            if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4667
0
            {
4668
0
                bCanWriteAreaOrPoint = false;
4669
0
                char *pszSQL = sqlite3_mprintf(
4670
0
                    "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4671
0
                    "grid_cell_encoding = 'grid-value-is-area' WHERE "
4672
0
                    "lower(tile_matrix_set_name) = lower('%q')",
4673
0
                    m_osRasterTable.c_str());
4674
0
                SQLCommand(hDB, pszSQL);
4675
0
                sqlite3_free(pszSQL);
4676
0
            }
4677
0
            else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4678
0
            {
4679
0
                bCanWriteAreaOrPoint = false;
4680
0
                char *pszSQL = sqlite3_mprintf(
4681
0
                    "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4682
0
                    "grid_cell_encoding = 'grid-value-is-center' WHERE "
4683
0
                    "lower(tile_matrix_set_name) = lower('%q')",
4684
0
                    m_osRasterTable.c_str());
4685
0
                SQLCommand(hDB, pszSQL);
4686
0
                sqlite3_free(pszSQL);
4687
0
            }
4688
0
        }
4689
0
    }
4690
4691
0
    char **papszMDDup = nullptr;
4692
0
    for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4693
0
         papszIter && *papszIter; ++papszIter)
4694
0
    {
4695
0
        if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4696
0
            continue;
4697
0
        if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4698
0
            continue;
4699
0
        if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4700
0
            continue;
4701
0
        if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4702
0
            continue;
4703
0
        if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4704
0
            !bCanWriteAreaOrPoint &&
4705
0
            STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4706
0
        {
4707
0
            continue;
4708
0
        }
4709
0
        papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4710
0
    }
4711
4712
0
    CPLXMLNode *psXMLNode = nullptr;
4713
0
    {
4714
0
        GDALMultiDomainMetadata oLocalMDMD;
4715
0
        CSLConstList papszDomainList = oMDMD.GetDomainList();
4716
0
        CSLConstList papszIter = papszDomainList;
4717
0
        oLocalMDMD.SetMetadata(papszMDDup);
4718
0
        while (papszIter && *papszIter)
4719
0
        {
4720
0
            if (!EQUAL(*papszIter, "") &&
4721
0
                !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4722
0
                !EQUAL(*papszIter, "GEOPACKAGE"))
4723
0
            {
4724
0
                oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4725
0
                                       *papszIter);
4726
0
            }
4727
0
            papszIter++;
4728
0
        }
4729
0
        if (m_nBandCountFromMetadata > 0)
4730
0
        {
4731
0
            oLocalMDMD.SetMetadataItem(
4732
0
                "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4733
0
                "IMAGE_STRUCTURE");
4734
0
            if (nBands == 1)
4735
0
            {
4736
0
                const auto poCT = GetRasterBand(1)->GetColorTable();
4737
0
                if (poCT)
4738
0
                {
4739
0
                    std::string osVal("{");
4740
0
                    const int nColorCount = poCT->GetColorEntryCount();
4741
0
                    for (int i = 0; i < nColorCount; ++i)
4742
0
                    {
4743
0
                        if (i > 0)
4744
0
                            osVal += ',';
4745
0
                        const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4746
0
                        osVal +=
4747
0
                            CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4748
0
                                       psEntry->c2, psEntry->c3, psEntry->c4);
4749
0
                    }
4750
0
                    osVal += '}';
4751
0
                    oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4752
0
                                               "IMAGE_STRUCTURE");
4753
0
                }
4754
0
            }
4755
0
            if (nBands == 1)
4756
0
            {
4757
0
                const char *pszTILE_FORMAT = nullptr;
4758
0
                switch (m_eTF)
4759
0
                {
4760
0
                    case GPKG_TF_PNG_JPEG:
4761
0
                        pszTILE_FORMAT = "JPEG_PNG";
4762
0
                        break;
4763
0
                    case GPKG_TF_PNG:
4764
0
                        break;
4765
0
                    case GPKG_TF_PNG8:
4766
0
                        pszTILE_FORMAT = "PNG8";
4767
0
                        break;
4768
0
                    case GPKG_TF_JPEG:
4769
0
                        pszTILE_FORMAT = "JPEG";
4770
0
                        break;
4771
0
                    case GPKG_TF_WEBP:
4772
0
                        pszTILE_FORMAT = "WEBP";
4773
0
                        break;
4774
0
                    case GPKG_TF_PNG_16BIT:
4775
0
                        break;
4776
0
                    case GPKG_TF_TIFF_32BIT_FLOAT:
4777
0
                        break;
4778
0
                }
4779
0
                if (pszTILE_FORMAT)
4780
0
                    oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4781
0
                                               "IMAGE_STRUCTURE");
4782
0
            }
4783
0
        }
4784
0
        if (GetRasterCount() > 0 &&
4785
0
            GetRasterBand(1)->GetRasterDataType() == GDT_Byte)
4786
0
        {
4787
0
            int bHasNoData = FALSE;
4788
0
            const double dfNoDataValue =
4789
0
                GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4790
0
            if (bHasNoData)
4791
0
            {
4792
0
                oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4793
0
                                           CPLSPrintf("%.17g", dfNoDataValue),
4794
0
                                           "IMAGE_STRUCTURE");
4795
0
            }
4796
0
        }
4797
0
        for (int i = 1; i <= GetRasterCount(); ++i)
4798
0
        {
4799
0
            auto poBand =
4800
0
                cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4801
0
            poBand->AddImplicitStatistics(false);
4802
0
            char **papszMD = GetRasterBand(i)->GetMetadata();
4803
0
            poBand->AddImplicitStatistics(true);
4804
0
            if (papszMD)
4805
0
            {
4806
0
                oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4807
0
            }
4808
0
        }
4809
0
        psXMLNode = oLocalMDMD.Serialize();
4810
0
    }
4811
4812
0
    CSLDestroy(papszMDDup);
4813
0
    papszMDDup = nullptr;
4814
4815
0
    WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4816
4817
0
    if (!m_osRasterTable.empty())
4818
0
    {
4819
0
        char **papszGeopackageMD =
4820
0
            GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4821
4822
0
        papszMDDup = nullptr;
4823
0
        for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4824
0
             ++papszIter)
4825
0
        {
4826
0
            papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4827
0
        }
4828
4829
0
        GDALMultiDomainMetadata oLocalMDMD;
4830
0
        oLocalMDMD.SetMetadata(papszMDDup);
4831
0
        CSLDestroy(papszMDDup);
4832
0
        papszMDDup = nullptr;
4833
0
        psXMLNode = oLocalMDMD.Serialize();
4834
4835
0
        WriteMetadata(psXMLNode, nullptr);
4836
0
    }
4837
4838
0
    for (auto &poLayer : m_apoLayers)
4839
0
    {
4840
0
        const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4841
0
        const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4842
0
        if (pszIdentifier != nullptr)
4843
0
        {
4844
0
            char *pszSQL =
4845
0
                sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4846
0
                                "WHERE lower(table_name) = lower('%q')",
4847
0
                                pszIdentifier, poLayer->GetName());
4848
0
            SQLCommand(hDB, pszSQL);
4849
0
            sqlite3_free(pszSQL);
4850
0
        }
4851
0
        if (pszDescription != nullptr)
4852
0
        {
4853
0
            char *pszSQL =
4854
0
                sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4855
0
                                "WHERE lower(table_name) = lower('%q')",
4856
0
                                pszDescription, poLayer->GetName());
4857
0
            SQLCommand(hDB, pszSQL);
4858
0
            sqlite3_free(pszSQL);
4859
0
        }
4860
4861
0
        papszMDDup = nullptr;
4862
0
        for (char **papszIter = poLayer->GetMetadata(); papszIter && *papszIter;
4863
0
             ++papszIter)
4864
0
        {
4865
0
            if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4866
0
                continue;
4867
0
            if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4868
0
                continue;
4869
0
            if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4870
0
                continue;
4871
0
            papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4872
0
        }
4873
4874
0
        {
4875
0
            GDALMultiDomainMetadata oLocalMDMD;
4876
0
            char **papszDomainList = poLayer->GetMetadataDomainList();
4877
0
            char **papszIter = papszDomainList;
4878
0
            oLocalMDMD.SetMetadata(papszMDDup);
4879
0
            while (papszIter && *papszIter)
4880
0
            {
4881
0
                if (!EQUAL(*papszIter, ""))
4882
0
                    oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4883
0
                                           *papszIter);
4884
0
                papszIter++;
4885
0
            }
4886
0
            CSLDestroy(papszDomainList);
4887
0
            psXMLNode = oLocalMDMD.Serialize();
4888
0
        }
4889
4890
0
        CSLDestroy(papszMDDup);
4891
0
        papszMDDup = nullptr;
4892
4893
0
        WriteMetadata(psXMLNode, poLayer->GetName());
4894
0
    }
4895
0
}
4896
4897
/************************************************************************/
4898
/*                          GetMetadataItem()                           */
4899
/************************************************************************/
4900
4901
const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4902
                                                   const char *pszDomain)
4903
1.81k
{
4904
1.81k
    pszDomain = CheckMetadataDomain(pszDomain);
4905
1.81k
    return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4906
1.81k
}
4907
4908
/************************************************************************/
4909
/*                            SetMetadata()                             */
4910
/************************************************************************/
4911
4912
CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
4913
                                          const char *pszDomain)
4914
0
{
4915
0
    pszDomain = CheckMetadataDomain(pszDomain);
4916
0
    m_bMetadataDirty = true;
4917
0
    GetMetadata(); /* force loading from storage if needed */
4918
0
    return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4919
0
}
4920
4921
/************************************************************************/
4922
/*                          SetMetadataItem()                           */
4923
/************************************************************************/
4924
4925
CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4926
                                              const char *pszValue,
4927
                                              const char *pszDomain)
4928
0
{
4929
0
    pszDomain = CheckMetadataDomain(pszDomain);
4930
0
    m_bMetadataDirty = true;
4931
0
    GetMetadata(); /* force loading from storage if needed */
4932
0
    return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4933
0
}
4934
4935
/************************************************************************/
4936
/*                                Create()                              */
4937
/************************************************************************/
4938
4939
int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4940
                                  int nYSize, int nBandsIn, GDALDataType eDT,
4941
                                  char **papszOptions)
4942
255
{
4943
255
    CPLString osCommand;
4944
4945
    /* First, ensure there isn't any such file yet. */
4946
255
    VSIStatBufL sStatBuf;
4947
4948
255
    if (nBandsIn != 0)
4949
0
    {
4950
0
        if (eDT == GDT_Byte)
4951
0
        {
4952
0
            if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4953
0
                nBandsIn != 4)
4954
0
            {
4955
0
                CPLError(CE_Failure, CPLE_NotSupported,
4956
0
                         "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4957
0
                         "3 (RGB) or 4 (RGBA) band dataset supported for "
4958
0
                         "Byte datatype");
4959
0
                return FALSE;
4960
0
            }
4961
0
        }
4962
0
        else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4963
0
        {
4964
0
            if (nBandsIn != 1)
4965
0
            {
4966
0
                CPLError(CE_Failure, CPLE_NotSupported,
4967
0
                         "Only single band dataset supported for non Byte "
4968
0
                         "datatype");
4969
0
                return FALSE;
4970
0
            }
4971
0
        }
4972
0
        else
4973
0
        {
4974
0
            CPLError(CE_Failure, CPLE_NotSupported,
4975
0
                     "Only Byte, Int16, UInt16 or Float32 supported");
4976
0
            return FALSE;
4977
0
        }
4978
0
    }
4979
4980
255
    const size_t nFilenameLen = strlen(pszFilename);
4981
255
    const bool bGpkgZip =
4982
255
        (nFilenameLen > strlen(".gpkg.zip") &&
4983
255
         !STARTS_WITH(pszFilename, "/vsizip/") &&
4984
255
         EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4985
4986
255
    const bool bUseTempFile =
4987
255
        bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4988
226
                         "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4989
226
                     (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4990
0
                      EQUAL(CPLGetConfigOption(
4991
0
                                "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4992
0
                            "FORCED")));
4993
4994
255
    bool bFileExists = false;
4995
255
    if (VSIStatL(pszFilename, &sStatBuf) == 0)
4996
0
    {
4997
0
        bFileExists = true;
4998
0
        if (nBandsIn == 0 || bUseTempFile ||
4999
0
            !CPLTestBool(
5000
0
                CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5001
0
        {
5002
0
            CPLError(CE_Failure, CPLE_AppDefined,
5003
0
                     "A file system object called '%s' already exists.",
5004
0
                     pszFilename);
5005
5006
0
            return FALSE;
5007
0
        }
5008
0
    }
5009
5010
255
    if (bUseTempFile)
5011
29
    {
5012
29
        if (bGpkgZip)
5013
29
        {
5014
29
            std::string osFilenameInZip(CPLGetFilename(pszFilename));
5015
29
            osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5016
29
            m_osFinalFilename =
5017
29
                std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5018
29
        }
5019
0
        else
5020
0
        {
5021
0
            m_osFinalFilename = pszFilename;
5022
0
        }
5023
29
        m_pszFilename = CPLStrdup(
5024
29
            CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5025
29
        CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5026
29
    }
5027
226
    else
5028
226
    {
5029
226
        m_pszFilename = CPLStrdup(pszFilename);
5030
226
    }
5031
255
    m_bNew = true;
5032
255
    eAccess = GA_Update;
5033
255
    m_bDateTimeWithTZ =
5034
255
        EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5035
255
              "WITH_TZ");
5036
5037
    // for test/debug purposes only. true is the nominal value
5038
255
    m_bPNGSupports2Bands =
5039
255
        CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5040
255
    m_bPNGSupportsCT =
5041
255
        CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5042
5043
255
    if (!OpenOrCreateDB(bFileExists
5044
255
                            ? SQLITE_OPEN_READWRITE
5045
255
                            : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5046
0
        return FALSE;
5047
5048
    /* Default to synchronous=off for performance for new file */
5049
255
    if (!bFileExists &&
5050
255
        CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5051
255
    {
5052
255
        SQLCommand(hDB, "PRAGMA synchronous = OFF");
5053
255
    }
5054
5055
    /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5056
    /* will be written into the main file and supported henceforth */
5057
255
    SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5058
5059
255
    if (bFileExists)
5060
0
    {
5061
0
        VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5062
0
        if (fp)
5063
0
        {
5064
0
            GByte abyHeader[100];
5065
0
            VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5066
0
            VSIFCloseL(fp);
5067
5068
0
            memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5069
0
            m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5070
0
            memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5071
0
            m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5072
5073
0
            if (m_nApplicationId == GP10_APPLICATION_ID)
5074
0
            {
5075
0
                CPLDebug("GPKG", "GeoPackage v1.0");
5076
0
            }
5077
0
            else if (m_nApplicationId == GP11_APPLICATION_ID)
5078
0
            {
5079
0
                CPLDebug("GPKG", "GeoPackage v1.1");
5080
0
            }
5081
0
            else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5082
0
                     m_nUserVersion >= GPKG_1_2_VERSION)
5083
0
            {
5084
0
                CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5085
0
                         (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5086
0
            }
5087
0
        }
5088
5089
0
        DetectSpatialRefSysColumns();
5090
0
    }
5091
5092
255
    const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5093
255
    if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5094
0
    {
5095
0
        if (EQUAL(pszVersion, "1.0"))
5096
0
        {
5097
0
            m_nApplicationId = GP10_APPLICATION_ID;
5098
0
            m_nUserVersion = 0;
5099
0
        }
5100
0
        else if (EQUAL(pszVersion, "1.1"))
5101
0
        {
5102
0
            m_nApplicationId = GP11_APPLICATION_ID;
5103
0
            m_nUserVersion = 0;
5104
0
        }
5105
0
        else if (EQUAL(pszVersion, "1.2"))
5106
0
        {
5107
0
            m_nApplicationId = GPKG_APPLICATION_ID;
5108
0
            m_nUserVersion = GPKG_1_2_VERSION;
5109
0
        }
5110
0
        else if (EQUAL(pszVersion, "1.3"))
5111
0
        {
5112
0
            m_nApplicationId = GPKG_APPLICATION_ID;
5113
0
            m_nUserVersion = GPKG_1_3_VERSION;
5114
0
        }
5115
0
        else if (EQUAL(pszVersion, "1.4"))
5116
0
        {
5117
0
            m_nApplicationId = GPKG_APPLICATION_ID;
5118
0
            m_nUserVersion = GPKG_1_4_VERSION;
5119
0
        }
5120
0
    }
5121
5122
255
    SoftStartTransaction();
5123
5124
255
    CPLString osSQL;
5125
255
    if (!bFileExists)
5126
255
    {
5127
        /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5128
         * table */
5129
        /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5130
255
        osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5131
255
                "srs_name TEXT NOT NULL,"
5132
255
                "srs_id INTEGER NOT NULL PRIMARY KEY,"
5133
255
                "organization TEXT NOT NULL,"
5134
255
                "organization_coordsys_id INTEGER NOT NULL,"
5135
255
                "definition  TEXT NOT NULL,"
5136
255
                "description TEXT";
5137
255
        if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5138
255
                                             "NO")) ||
5139
255
            (nBandsIn != 0 && eDT != GDT_Byte))
5140
0
        {
5141
0
            m_bHasDefinition12_063 = true;
5142
0
            osSQL += ", definition_12_063 TEXT NOT NULL";
5143
0
            if (m_nUserVersion >= GPKG_1_4_VERSION)
5144
0
            {
5145
0
                osSQL += ", epoch DOUBLE";
5146
0
                m_bHasEpochColumn = true;
5147
0
            }
5148
0
        }
5149
255
        osSQL += ")"
5150
255
                 ";"
5151
                 /* Requirement 11: The gpkg_spatial_ref_sys table in a
5152
                    GeoPackage SHALL */
5153
                 /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5154
                 /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5155
5156
255
                 "INSERT INTO gpkg_spatial_ref_sys ("
5157
255
                 "srs_name, srs_id, organization, organization_coordsys_id, "
5158
255
                 "definition, description";
5159
255
        if (m_bHasDefinition12_063)
5160
0
            osSQL += ", definition_12_063";
5161
255
        osSQL +=
5162
255
            ") VALUES ("
5163
255
            "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5164
255
            "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5165
255
            "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5166
255
            "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5167
255
            "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5168
255
            "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5169
255
            "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5170
255
            "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5171
255
            "84 spheroid'";
5172
255
        if (m_bHasDefinition12_063)
5173
0
            osSQL +=
5174
0
                ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5175
0
                "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5176
0
                "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5177
0
                "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5178
0
                "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5179
0
                "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5180
0
                "ID[\"EPSG\", 4326]]'";
5181
255
        osSQL +=
5182
255
            ")"
5183
255
            ";"
5184
            /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5185
               SHALL */
5186
            /* contain a record with an srs_id of -1, an organization of “NONE”,
5187
             */
5188
            /* an organization_coordsys_id of -1, and definition “undefined” */
5189
            /* for undefined Cartesian coordinate reference systems */
5190
            /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5191
255
            "INSERT INTO gpkg_spatial_ref_sys ("
5192
255
            "srs_name, srs_id, organization, organization_coordsys_id, "
5193
255
            "definition, description";
5194
255
        if (m_bHasDefinition12_063)
5195
0
            osSQL += ", definition_12_063";
5196
255
        osSQL += ") VALUES ("
5197
255
                 "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5198
255
                 "'undefined Cartesian coordinate reference system'";
5199
255
        if (m_bHasDefinition12_063)
5200
0
            osSQL += ", 'undefined'";
5201
255
        osSQL +=
5202
255
            ")"
5203
255
            ";"
5204
            /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5205
               SHALL */
5206
            /* contain a record with an srs_id of 0, an organization of “NONE”,
5207
             */
5208
            /* an organization_coordsys_id of 0, and definition “undefined” */
5209
            /* for undefined geographic coordinate reference systems */
5210
            /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5211
255
            "INSERT INTO gpkg_spatial_ref_sys ("
5212
255
            "srs_name, srs_id, organization, organization_coordsys_id, "
5213
255
            "definition, description";
5214
255
        if (m_bHasDefinition12_063)
5215
0
            osSQL += ", definition_12_063";
5216
255
        osSQL += ") VALUES ("
5217
255
                 "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5218
255
                 "'undefined geographic coordinate reference system'";
5219
255
        if (m_bHasDefinition12_063)
5220
0
            osSQL += ", 'undefined'";
5221
255
        osSQL += ")"
5222
255
                 ";"
5223
                 /* Requirement 13: A GeoPackage file SHALL include a
5224
                    gpkg_contents table */
5225
                 /* http://opengis.github.io/geopackage/#_contents */
5226
255
                 "CREATE TABLE gpkg_contents ("
5227
255
                 "table_name TEXT NOT NULL PRIMARY KEY,"
5228
255
                 "data_type TEXT NOT NULL,"
5229
255
                 "identifier TEXT UNIQUE,"
5230
255
                 "description TEXT DEFAULT '',"
5231
255
                 "last_change DATETIME NOT NULL DEFAULT "
5232
255
                 "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5233
255
                 "min_x DOUBLE, min_y DOUBLE,"
5234
255
                 "max_x DOUBLE, max_y DOUBLE,"
5235
255
                 "srs_id INTEGER,"
5236
255
                 "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5237
255
                 "gpkg_spatial_ref_sys(srs_id)"
5238
255
                 ")";
5239
5240
255
#ifdef ENABLE_GPKG_OGR_CONTENTS
5241
255
        if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5242
255
        {
5243
255
            m_bHasGPKGOGRContents = true;
5244
255
            osSQL += ";"
5245
255
                     "CREATE TABLE gpkg_ogr_contents("
5246
255
                     "table_name TEXT NOT NULL PRIMARY KEY,"
5247
255
                     "feature_count INTEGER DEFAULT NULL"
5248
255
                     ")";
5249
255
        }
5250
255
#endif
5251
5252
        /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5253
         * “features” */
5254
        /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5255
         * view */
5256
        /* http://opengis.github.io/geopackage/#_geometry_columns */
5257
255
        const bool bCreateGeometryColumns =
5258
255
            CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5259
255
        if (bCreateGeometryColumns)
5260
255
        {
5261
255
            m_bHasGPKGGeometryColumns = true;
5262
255
            osSQL += ";";
5263
255
            osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5264
255
        }
5265
255
    }
5266
5267
255
    const bool bCreateTriggers =
5268
255
        CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5269
255
    if ((bFileExists && nBandsIn != 0 &&
5270
255
         SQLGetInteger(
5271
0
             hDB,
5272
0
             "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5273
0
             "AND type in ('table', 'view')",
5274
0
             nullptr) == 0) ||
5275
255
        (!bFileExists &&
5276
255
         CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5277
255
    {
5278
255
        if (!osSQL.empty())
5279
255
            osSQL += ";";
5280
5281
        /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5282
         * Creation SQL  */
5283
255
        osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5284
255
                 "table_name TEXT NOT NULL PRIMARY KEY,"
5285
255
                 "srs_id INTEGER NOT NULL,"
5286
255
                 "min_x DOUBLE NOT NULL,"
5287
255
                 "min_y DOUBLE NOT NULL,"
5288
255
                 "max_x DOUBLE NOT NULL,"
5289
255
                 "max_y DOUBLE NOT NULL,"
5290
255
                 "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5291
255
                 "REFERENCES gpkg_contents(table_name),"
5292
255
                 "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5293
255
                 "gpkg_spatial_ref_sys (srs_id)"
5294
255
                 ")"
5295
255
                 ";"
5296
5297
                 /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5298
                    Creation SQL */
5299
255
                 "CREATE TABLE gpkg_tile_matrix ("
5300
255
                 "table_name TEXT NOT NULL,"
5301
255
                 "zoom_level INTEGER NOT NULL,"
5302
255
                 "matrix_width INTEGER NOT NULL,"
5303
255
                 "matrix_height INTEGER NOT NULL,"
5304
255
                 "tile_width INTEGER NOT NULL,"
5305
255
                 "tile_height INTEGER NOT NULL,"
5306
255
                 "pixel_x_size DOUBLE NOT NULL,"
5307
255
                 "pixel_y_size DOUBLE NOT NULL,"
5308
255
                 "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5309
255
                 "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5310
255
                 "REFERENCES gpkg_contents(table_name)"
5311
255
                 ")";
5312
5313
255
        if (bCreateTriggers)
5314
255
        {
5315
            /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5316
             * Definition SQL */
5317
255
            const char *pszTileMatrixTrigger =
5318
255
                "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5319
255
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5320
255
                "FOR EACH ROW BEGIN "
5321
255
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5322
255
                "violates constraint: zoom_level cannot be less than 0') "
5323
255
                "WHERE (NEW.zoom_level < 0); "
5324
255
                "END; "
5325
255
                "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5326
255
                "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5327
255
                "FOR EACH ROW BEGIN "
5328
255
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5329
255
                "violates constraint: zoom_level cannot be less than 0') "
5330
255
                "WHERE (NEW.zoom_level < 0); "
5331
255
                "END; "
5332
255
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5333
255
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5334
255
                "FOR EACH ROW BEGIN "
5335
255
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5336
255
                "violates constraint: matrix_width cannot be less than 1') "
5337
255
                "WHERE (NEW.matrix_width < 1); "
5338
255
                "END; "
5339
255
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5340
255
                "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5341
255
                "FOR EACH ROW BEGIN "
5342
255
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5343
255
                "violates constraint: matrix_width cannot be less than 1') "
5344
255
                "WHERE (NEW.matrix_width < 1); "
5345
255
                "END; "
5346
255
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5347
255
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5348
255
                "FOR EACH ROW BEGIN "
5349
255
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5350
255
                "violates constraint: matrix_height cannot be less than 1') "
5351
255
                "WHERE (NEW.matrix_height < 1); "
5352
255
                "END; "
5353
255
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5354
255
                "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5355
255
                "FOR EACH ROW BEGIN "
5356
255
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5357
255
                "violates constraint: matrix_height cannot be less than 1') "
5358
255
                "WHERE (NEW.matrix_height < 1); "
5359
255
                "END; "
5360
255
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5361
255
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5362
255
                "FOR EACH ROW BEGIN "
5363
255
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5364
255
                "violates constraint: pixel_x_size must be greater than 0') "
5365
255
                "WHERE NOT (NEW.pixel_x_size > 0); "
5366
255
                "END; "
5367
255
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5368
255
                "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5369
255
                "FOR EACH ROW BEGIN "
5370
255
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5371
255
                "violates constraint: pixel_x_size must be greater than 0') "
5372
255
                "WHERE NOT (NEW.pixel_x_size > 0); "
5373
255
                "END; "
5374
255
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5375
255
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5376
255
                "FOR EACH ROW BEGIN "
5377
255
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5378
255
                "violates constraint: pixel_y_size must be greater than 0') "
5379
255
                "WHERE NOT (NEW.pixel_y_size > 0); "
5380
255
                "END; "
5381
255
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5382
255
                "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5383
255
                "FOR EACH ROW BEGIN "
5384
255
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5385
255
                "violates constraint: pixel_y_size must be greater than 0') "
5386
255
                "WHERE NOT (NEW.pixel_y_size > 0); "
5387
255
                "END;";
5388
255
            osSQL += ";";
5389
255
            osSQL += pszTileMatrixTrigger;
5390
255
        }
5391
255
    }
5392
5393
255
    if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5394
0
        return FALSE;
5395
5396
255
    if (!bFileExists)
5397
255
    {
5398
255
        const char *pszMetadataTables =
5399
255
            CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5400
255
        if (pszMetadataTables)
5401
0
            m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5402
5403
255
        if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5404
0
            return FALSE;
5405
5406
255
        if (m_bHasDefinition12_063)
5407
0
        {
5408
0
            if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5409
0
                OGRERR_NONE !=
5410
0
                    SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5411
0
                                    "(table_name, column_name, extension_name, "
5412
0
                                    "definition, scope) "
5413
0
                                    "VALUES "
5414
0
                                    "('gpkg_spatial_ref_sys', "
5415
0
                                    "'definition_12_063', 'gpkg_crs_wkt', "
5416
0
                                    "'http://www.geopackage.org/spec120/"
5417
0
                                    "#extension_crs_wkt', 'read-write')"))
5418
0
            {
5419
0
                return FALSE;
5420
0
            }
5421
0
            if (m_bHasEpochColumn)
5422
0
            {
5423
0
                if (OGRERR_NONE !=
5424
0
                        SQLCommand(
5425
0
                            hDB, "UPDATE gpkg_extensions SET extension_name = "
5426
0
                                 "'gpkg_crs_wkt_1_1' "
5427
0
                                 "WHERE extension_name = 'gpkg_crs_wkt'") ||
5428
0
                    OGRERR_NONE !=
5429
0
                        SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5430
0
                                        "(table_name, column_name, "
5431
0
                                        "extension_name, definition, scope) "
5432
0
                                        "VALUES "
5433
0
                                        "('gpkg_spatial_ref_sys', 'epoch', "
5434
0
                                        "'gpkg_crs_wkt_1_1', "
5435
0
                                        "'http://www.geopackage.org/spec/"
5436
0
                                        "#extension_crs_wkt', "
5437
0
                                        "'read-write')"))
5438
0
                {
5439
0
                    return FALSE;
5440
0
                }
5441
0
            }
5442
0
        }
5443
255
    }
5444
5445
255
    if (nBandsIn != 0)
5446
0
    {
5447
0
        const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5448
0
        m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5449
0
                                               osTableName.c_str());
5450
0
        if (m_osRasterTable.empty())
5451
0
        {
5452
0
            CPLError(CE_Failure, CPLE_AppDefined,
5453
0
                     "RASTER_TABLE must be set to a non empty value");
5454
0
            return FALSE;
5455
0
        }
5456
0
        m_bIdentifierAsCO =
5457
0
            CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5458
0
        m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5459
0
                                              m_osRasterTable);
5460
0
        m_bDescriptionAsCO =
5461
0
            CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5462
0
        m_osDescription =
5463
0
            CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5464
0
        SetDataType(eDT);
5465
0
        if (eDT == GDT_Int16)
5466
0
            SetGlobalOffsetScale(-32768.0, 1.0);
5467
5468
        /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5469
         * table Create Table SQL (Informative) */
5470
0
        char *pszSQL =
5471
0
            sqlite3_mprintf("CREATE TABLE \"%w\" ("
5472
0
                            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5473
0
                            "zoom_level INTEGER NOT NULL,"
5474
0
                            "tile_column INTEGER NOT NULL,"
5475
0
                            "tile_row INTEGER NOT NULL,"
5476
0
                            "tile_data BLOB NOT NULL,"
5477
0
                            "UNIQUE (zoom_level, tile_column, tile_row)"
5478
0
                            ")",
5479
0
                            m_osRasterTable.c_str());
5480
0
        osSQL = pszSQL;
5481
0
        sqlite3_free(pszSQL);
5482
5483
0
        if (bCreateTriggers)
5484
0
        {
5485
0
            osSQL += ";";
5486
0
            osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5487
0
        }
5488
5489
0
        OGRErr eErr = SQLCommand(hDB, osSQL);
5490
0
        if (OGRERR_NONE != eErr)
5491
0
            return FALSE;
5492
5493
0
        const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5494
0
        if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5495
0
        {
5496
0
            m_eTF = GPKG_TF_PNG_16BIT;
5497
0
            if (pszTF)
5498
0
            {
5499
0
                if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5500
0
                {
5501
0
                    CPLError(CE_Warning, CPLE_NotSupported,
5502
0
                             "Only AUTO or PNG supported "
5503
0
                             "as tile format for Int16 / UInt16");
5504
0
                }
5505
0
            }
5506
0
        }
5507
0
        else if (eDT == GDT_Float32)
5508
0
        {
5509
0
            m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5510
0
            if (pszTF)
5511
0
            {
5512
0
                if (EQUAL(pszTF, "PNG"))
5513
0
                    m_eTF = GPKG_TF_PNG_16BIT;
5514
0
                else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5515
0
                {
5516
0
                    CPLError(CE_Warning, CPLE_NotSupported,
5517
0
                             "Only AUTO, PNG or TIFF supported "
5518
0
                             "as tile format for Float32");
5519
0
                }
5520
0
            }
5521
0
        }
5522
0
        else
5523
0
        {
5524
0
            if (pszTF)
5525
0
            {
5526
0
                m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5527
0
                if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5528
0
                    m_bMetadataDirty = true;
5529
0
            }
5530
0
            else if (nBandsIn == 1)
5531
0
                m_eTF = GPKG_TF_PNG;
5532
0
        }
5533
5534
0
        if (eDT != GDT_Byte)
5535
0
        {
5536
0
            if (!CreateTileGriddedTable(papszOptions))
5537
0
                return FALSE;
5538
0
        }
5539
5540
0
        nRasterXSize = nXSize;
5541
0
        nRasterYSize = nYSize;
5542
5543
0
        const char *pszTileSize =
5544
0
            CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5545
0
        const char *pszTileWidth =
5546
0
            CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5547
0
        const char *pszTileHeight =
5548
0
            CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5549
0
        int nTileWidth = atoi(pszTileWidth);
5550
0
        int nTileHeight = atoi(pszTileHeight);
5551
0
        if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5552
0
             nTileHeight > 4096) &&
5553
0
            !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5554
0
        {
5555
0
            CPLError(CE_Failure, CPLE_AppDefined,
5556
0
                     "Invalid block dimensions: %dx%d", nTileWidth,
5557
0
                     nTileHeight);
5558
0
            return FALSE;
5559
0
        }
5560
5561
0
        for (int i = 1; i <= nBandsIn; i++)
5562
0
        {
5563
0
            SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5564
0
                           this, nTileWidth, nTileHeight));
5565
0
        }
5566
5567
0
        GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5568
0
                                        "IMAGE_STRUCTURE");
5569
0
        GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5570
0
        if (!m_osDescription.empty())
5571
0
            GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5572
5573
0
        ParseCompressionOptions(papszOptions);
5574
5575
0
        if (m_eTF == GPKG_TF_WEBP)
5576
0
        {
5577
0
            if (!RegisterWebPExtension())
5578
0
                return FALSE;
5579
0
        }
5580
5581
0
        m_osTilingScheme =
5582
0
            CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5583
0
        if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5584
0
        {
5585
0
            const auto poTS = GetTilingScheme(m_osTilingScheme);
5586
0
            if (!poTS)
5587
0
                return FALSE;
5588
5589
0
            if (nTileWidth != poTS->nTileWidth ||
5590
0
                nTileHeight != poTS->nTileHeight)
5591
0
            {
5592
0
                CPLError(CE_Failure, CPLE_NotSupported,
5593
0
                         "Tile dimension should be %dx%d for %s tiling scheme",
5594
0
                         poTS->nTileWidth, poTS->nTileHeight,
5595
0
                         m_osTilingScheme.c_str());
5596
0
                return FALSE;
5597
0
            }
5598
5599
0
            const char *pszZoomLevel =
5600
0
                CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5601
0
            if (pszZoomLevel)
5602
0
            {
5603
0
                m_nZoomLevel = atoi(pszZoomLevel);
5604
0
                int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5605
0
                while ((1 << nMaxZoomLevelForThisTM) >
5606
0
                           INT_MAX / poTS->nTileXCountZoomLevel0 ||
5607
0
                       (1 << nMaxZoomLevelForThisTM) >
5608
0
                           INT_MAX / poTS->nTileYCountZoomLevel0)
5609
0
                {
5610
0
                    --nMaxZoomLevelForThisTM;
5611
0
                }
5612
5613
0
                if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5614
0
                {
5615
0
                    CPLError(CE_Failure, CPLE_AppDefined,
5616
0
                             "ZOOM_LEVEL = %s is invalid. It should be in "
5617
0
                             "[0,%d] range",
5618
0
                             pszZoomLevel, nMaxZoomLevelForThisTM);
5619
0
                    return FALSE;
5620
0
                }
5621
0
            }
5622
5623
            // Implicitly sets SRS.
5624
0
            OGRSpatialReference oSRS;
5625
0
            if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5626
0
                return FALSE;
5627
0
            char *pszWKT = nullptr;
5628
0
            oSRS.exportToWkt(&pszWKT);
5629
0
            SetProjection(pszWKT);
5630
0
            CPLFree(pszWKT);
5631
0
        }
5632
0
        else
5633
0
        {
5634
0
            if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5635
0
            {
5636
0
                CPLError(
5637
0
                    CE_Failure, CPLE_NotSupported,
5638
0
                    "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5639
0
                return false;
5640
0
            }
5641
0
        }
5642
0
    }
5643
5644
255
    if (bFileExists && nBandsIn > 0 && eDT == GDT_Byte)
5645
0
    {
5646
        // If there was an ogr_empty_table table, we can remove it
5647
0
        RemoveOGREmptyTable();
5648
0
    }
5649
5650
255
    SoftCommitTransaction();
5651
5652
    /* Requirement 2 */
5653
    /* We have to do this after there's some content so the database file */
5654
    /* is not zero length */
5655
255
    SetApplicationAndUserVersionId();
5656
5657
    /* Default to synchronous=off for performance for new file */
5658
255
    if (!bFileExists &&
5659
255
        CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5660
255
    {
5661
255
        SQLCommand(hDB, "PRAGMA synchronous = OFF");
5662
255
    }
5663
5664
255
    return TRUE;
5665
255
}
5666
5667
/************************************************************************/
5668
/*                        RemoveOGREmptyTable()                         */
5669
/************************************************************************/
5670
5671
void GDALGeoPackageDataset::RemoveOGREmptyTable()
5672
253
{
5673
    // Run with sqlite3_exec since we don't want errors to be emitted
5674
253
    sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5675
253
                 nullptr);
5676
253
    sqlite3_exec(
5677
253
        hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5678
253
        nullptr, nullptr, nullptr);
5679
253
#ifdef ENABLE_GPKG_OGR_CONTENTS
5680
253
    if (m_bHasGPKGOGRContents)
5681
253
    {
5682
253
        sqlite3_exec(hDB,
5683
253
                     "DELETE FROM gpkg_ogr_contents WHERE "
5684
253
                     "table_name = 'ogr_empty_table'",
5685
253
                     nullptr, nullptr, nullptr);
5686
253
    }
5687
253
#endif
5688
253
    sqlite3_exec(hDB,
5689
253
                 "DELETE FROM gpkg_geometry_columns WHERE "
5690
253
                 "table_name = 'ogr_empty_table'",
5691
253
                 nullptr, nullptr, nullptr);
5692
253
}
5693
5694
/************************************************************************/
5695
/*                        CreateTileGriddedTable()                      */
5696
/************************************************************************/
5697
5698
bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5699
0
{
5700
0
    CPLString osSQL;
5701
0
    if (!HasGriddedCoverageAncillaryTable())
5702
0
    {
5703
        // It doesn't exist. So create gpkg_extensions table if necessary, and
5704
        // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5705
        // and register them as extensions.
5706
0
        if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5707
0
            return false;
5708
5709
        // Req 1 /table-defs/coverage-ancillary
5710
0
        osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5711
0
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5712
0
                "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5713
0
                "datatype TEXT NOT NULL DEFAULT 'integer',"
5714
0
                "scale REAL NOT NULL DEFAULT 1.0,"
5715
0
                "offset REAL NOT NULL DEFAULT 0.0,"
5716
0
                "precision REAL DEFAULT 1.0,"
5717
0
                "data_null REAL,"
5718
0
                "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5719
0
                "uom TEXT,"
5720
0
                "field_name TEXT DEFAULT 'Height',"
5721
0
                "quantity_definition TEXT DEFAULT 'Height',"
5722
0
                "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5723
0
                "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5724
0
                "CHECK (datatype in ('integer','float')))"
5725
0
                ";"
5726
                // Requirement 2 /table-defs/tile-ancillary
5727
0
                "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5728
0
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5729
0
                "tpudt_name TEXT NOT NULL,"
5730
0
                "tpudt_id INTEGER NOT NULL,"
5731
0
                "scale REAL NOT NULL DEFAULT 1.0,"
5732
0
                "offset REAL NOT NULL DEFAULT 0.0,"
5733
0
                "min REAL DEFAULT NULL,"
5734
0
                "max REAL DEFAULT NULL,"
5735
0
                "mean REAL DEFAULT NULL,"
5736
0
                "std_dev REAL DEFAULT NULL,"
5737
0
                "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5738
0
                "REFERENCES gpkg_contents(table_name),"
5739
0
                "UNIQUE (tpudt_name, tpudt_id))"
5740
0
                ";"
5741
                // Requirement 6 /gpkg-extensions
5742
0
                "INSERT INTO gpkg_extensions "
5743
0
                "(table_name, column_name, extension_name, definition, scope) "
5744
0
                "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5745
0
                "'gpkg_2d_gridded_coverage', "
5746
0
                "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5747
0
                "'read-write')"
5748
0
                ";"
5749
                // Requirement 6 /gpkg-extensions
5750
0
                "INSERT INTO gpkg_extensions "
5751
0
                "(table_name, column_name, extension_name, definition, scope) "
5752
0
                "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5753
0
                "'gpkg_2d_gridded_coverage', "
5754
0
                "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5755
0
                "'read-write')"
5756
0
                ";";
5757
0
    }
5758
5759
    // Requirement 6 /gpkg-extensions
5760
0
    char *pszSQL = sqlite3_mprintf(
5761
0
        "INSERT INTO gpkg_extensions "
5762
0
        "(table_name, column_name, extension_name, definition, scope) "
5763
0
        "VALUES ('%q', 'tile_data', "
5764
0
        "'gpkg_2d_gridded_coverage', "
5765
0
        "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5766
0
        "'read-write')",
5767
0
        m_osRasterTable.c_str());
5768
0
    osSQL += pszSQL;
5769
0
    osSQL += ";";
5770
0
    sqlite3_free(pszSQL);
5771
5772
    // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5773
    // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5774
    // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5775
0
    m_dfPrecision =
5776
0
        CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5777
0
    CPLString osGridCellEncoding(CSLFetchNameValueDef(
5778
0
        papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5779
0
    m_bGridCellEncodingAsCO =
5780
0
        CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5781
0
    CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5782
0
    CPLString osFieldName(
5783
0
        CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5784
0
    CPLString osQuantityDefinition(
5785
0
        CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5786
5787
0
    pszSQL = sqlite3_mprintf(
5788
0
        "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5789
0
        "(tile_matrix_set_name, datatype, scale, offset, precision, "
5790
0
        "grid_cell_encoding, uom, field_name, quantity_definition) "
5791
0
        "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5792
0
        m_osRasterTable.c_str(),
5793
0
        (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5794
0
        m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5795
0
        osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5796
0
        osQuantityDefinition.c_str());
5797
0
    m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5798
0
    sqlite3_free(pszSQL);
5799
5800
    // Requirement 3 /gpkg-spatial-ref-sys-row
5801
0
    auto oResultTable = SQLQuery(
5802
0
        hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5803
0
    bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5804
0
    if (!bHasEPSG4979)
5805
0
    {
5806
0
        if (!m_bHasDefinition12_063 &&
5807
0
            !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5808
0
        {
5809
0
            return false;
5810
0
        }
5811
5812
        // This is WKT 2...
5813
0
        const char *pszWKT =
5814
0
            "GEODCRS[\"WGS 84\","
5815
0
            "DATUM[\"World Geodetic System 1984\","
5816
0
            "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5817
0
            "LENGTHUNIT[\"metre\",1.0]]],"
5818
0
            "CS[ellipsoidal,3],"
5819
0
            "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5820
0
            "0.0174532925199433]],"
5821
0
            "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5822
0
            "0.0174532925199433]],"
5823
0
            "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
5824
0
            "LENGTHUNIT[\"metre\",1.0]],"
5825
0
            "ID[\"EPSG\",4979]]";
5826
5827
0
        pszSQL = sqlite3_mprintf(
5828
0
            "INSERT INTO gpkg_spatial_ref_sys "
5829
0
            "(srs_name,srs_id,organization,organization_coordsys_id,"
5830
0
            "definition,definition_12_063) VALUES "
5831
0
            "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5832
0
            pszWKT);
5833
0
        osSQL += ";";
5834
0
        osSQL += pszSQL;
5835
0
        sqlite3_free(pszSQL);
5836
0
    }
5837
5838
0
    return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5839
0
}
5840
5841
/************************************************************************/
5842
/*                    HasGriddedCoverageAncillaryTable()                */
5843
/************************************************************************/
5844
5845
bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5846
0
{
5847
0
    auto oResultTable = SQLQuery(
5848
0
        hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5849
0
             "name = 'gpkg_2d_gridded_coverage_ancillary'");
5850
0
    bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5851
0
    return bHasTable;
5852
0
}
5853
5854
/************************************************************************/
5855
/*                      GetUnderlyingDataset()                          */
5856
/************************************************************************/
5857
5858
static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5859
0
{
5860
0
    if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5861
0
    {
5862
0
        auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5863
0
        if (poTmpDS)
5864
0
            return poTmpDS;
5865
0
    }
5866
5867
0
    return poSrcDS;
5868
0
}
5869
5870
/************************************************************************/
5871
/*                            CreateCopy()                              */
5872
/************************************************************************/
5873
5874
typedef struct
5875
{
5876
    const char *pszName;
5877
    GDALResampleAlg eResampleAlg;
5878
} WarpResamplingAlg;
5879
5880
static const WarpResamplingAlg asResamplingAlg[] = {
5881
    {"NEAREST", GRA_NearestNeighbour},
5882
    {"BILINEAR", GRA_Bilinear},
5883
    {"CUBIC", GRA_Cubic},
5884
    {"CUBICSPLINE", GRA_CubicSpline},
5885
    {"LANCZOS", GRA_Lanczos},
5886
    {"MODE", GRA_Mode},
5887
    {"AVERAGE", GRA_Average},
5888
    {"RMS", GRA_RMS},
5889
};
5890
5891
GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5892
                                               GDALDataset *poSrcDS,
5893
                                               int bStrict, char **papszOptions,
5894
                                               GDALProgressFunc pfnProgress,
5895
                                               void *pProgressData)
5896
0
{
5897
0
    const int nBands = poSrcDS->GetRasterCount();
5898
0
    if (nBands == 0)
5899
0
    {
5900
0
        GDALDataset *poDS = nullptr;
5901
0
        GDALDriver *poThisDriver =
5902
0
            GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5903
0
        if (poThisDriver != nullptr)
5904
0
        {
5905
0
            poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5906
0
                                                   bStrict, papszOptions,
5907
0
                                                   pfnProgress, pProgressData);
5908
0
        }
5909
0
        return poDS;
5910
0
    }
5911
5912
0
    const char *pszTilingScheme =
5913
0
        CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5914
5915
0
    CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5916
0
    if (CPLTestBool(
5917
0
            CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5918
0
        CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5919
0
    {
5920
0
        const std::string osBasename(CPLGetBasenameSafe(
5921
0
            GetUnderlyingDataset(poSrcDS)->GetDescription()));
5922
0
        apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5923
0
    }
5924
5925
0
    if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5926
0
    {
5927
0
        CPLError(CE_Failure, CPLE_NotSupported,
5928
0
                 "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5929
0
                 "4 (RGBA) band dataset supported");
5930
0
        return nullptr;
5931
0
    }
5932
5933
0
    const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5934
0
    if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5935
0
        !EQUAL(pszUnitType, ""))
5936
0
    {
5937
0
        apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5938
0
    }
5939
5940
0
    if (EQUAL(pszTilingScheme, "CUSTOM"))
5941
0
    {
5942
0
        if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5943
0
        {
5944
0
            CPLError(CE_Failure, CPLE_NotSupported,
5945
0
                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5946
0
            return nullptr;
5947
0
        }
5948
5949
0
        GDALGeoPackageDataset *poDS = nullptr;
5950
0
        GDALDriver *poThisDriver =
5951
0
            GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5952
0
        if (poThisDriver != nullptr)
5953
0
        {
5954
0
            apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5955
0
            poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5956
0
                poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5957
0
                                                apszUpdatedOptions, pfnProgress,
5958
0
                                                pProgressData));
5959
5960
0
            if (poDS != nullptr &&
5961
0
                poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_Byte &&
5962
0
                nBands <= 3)
5963
0
            {
5964
0
                poDS->m_nBandCountFromMetadata = nBands;
5965
0
                poDS->m_bMetadataDirty = true;
5966
0
            }
5967
0
        }
5968
0
        if (poDS)
5969
0
            poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5970
0
        return poDS;
5971
0
    }
5972
5973
0
    const auto poTS = GetTilingScheme(pszTilingScheme);
5974
0
    if (!poTS)
5975
0
    {
5976
0
        return nullptr;
5977
0
    }
5978
0
    const int nEPSGCode = poTS->nEPSGCode;
5979
5980
0
    OGRSpatialReference oSRS;
5981
0
    if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5982
0
    {
5983
0
        return nullptr;
5984
0
    }
5985
0
    char *pszWKT = nullptr;
5986
0
    oSRS.exportToWkt(&pszWKT);
5987
0
    char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5988
5989
0
    void *hTransformArg = nullptr;
5990
5991
    // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
5992
    // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
5993
    // EPSG:3857.
5994
0
    double adfSrcGeoTransform[6];
5995
0
    std::unique_ptr<GDALDataset> poTmpDS;
5996
0
    bool bEPSG3857Adjust = false;
5997
0
    if (nEPSGCode == 3857 &&
5998
0
        poSrcDS->GetGeoTransform(adfSrcGeoTransform) == CE_None &&
5999
0
        adfSrcGeoTransform[2] == 0 && adfSrcGeoTransform[4] == 0 &&
6000
0
        adfSrcGeoTransform[5] < 0)
6001
0
    {
6002
0
        const auto poSrcSRS = poSrcDS->GetSpatialRef();
6003
0
        if (poSrcSRS && poSrcSRS->IsGeographic())
6004
0
        {
6005
0
            double maxLat = adfSrcGeoTransform[3];
6006
0
            double minLat = adfSrcGeoTransform[3] +
6007
0
                            poSrcDS->GetRasterYSize() * adfSrcGeoTransform[5];
6008
            // Corresponds to the latitude of below MAX_GM
6009
0
            constexpr double MAX_LAT = 85.0511287798066;
6010
0
            bool bModified = false;
6011
0
            if (maxLat > MAX_LAT)
6012
0
            {
6013
0
                maxLat = MAX_LAT;
6014
0
                bModified = true;
6015
0
            }
6016
0
            if (minLat < -MAX_LAT)
6017
0
            {
6018
0
                minLat = -MAX_LAT;
6019
0
                bModified = true;
6020
0
            }
6021
0
            if (bModified)
6022
0
            {
6023
0
                CPLStringList aosOptions;
6024
0
                aosOptions.AddString("-of");
6025
0
                aosOptions.AddString("VRT");
6026
0
                aosOptions.AddString("-projwin");
6027
0
                aosOptions.AddString(
6028
0
                    CPLSPrintf("%.17g", adfSrcGeoTransform[0]));
6029
0
                aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6030
0
                aosOptions.AddString(
6031
0
                    CPLSPrintf("%.17g", adfSrcGeoTransform[0] +
6032
0
                                            poSrcDS->GetRasterXSize() *
6033
0
                                                adfSrcGeoTransform[1]));
6034
0
                aosOptions.AddString(CPLSPrintf("%.17g", minLat));
6035
0
                auto psOptions =
6036
0
                    GDALTranslateOptionsNew(aosOptions.List(), nullptr);
6037
0
                poTmpDS.reset(GDALDataset::FromHandle(GDALTranslate(
6038
0
                    "", GDALDataset::ToHandle(poSrcDS), psOptions, nullptr)));
6039
0
                GDALTranslateOptionsFree(psOptions);
6040
0
                if (poTmpDS)
6041
0
                {
6042
0
                    bEPSG3857Adjust = true;
6043
0
                    hTransformArg = GDALCreateGenImgProjTransformer2(
6044
0
                        GDALDataset::FromHandle(poTmpDS.get()), nullptr,
6045
0
                        papszTO);
6046
0
                }
6047
0
            }
6048
0
        }
6049
0
    }
6050
0
    if (hTransformArg == nullptr)
6051
0
    {
6052
0
        hTransformArg =
6053
0
            GDALCreateGenImgProjTransformer2(poSrcDS, nullptr, papszTO);
6054
0
    }
6055
6056
0
    if (hTransformArg == nullptr)
6057
0
    {
6058
0
        CPLFree(pszWKT);
6059
0
        CSLDestroy(papszTO);
6060
0
        return nullptr;
6061
0
    }
6062
6063
0
    GDALTransformerInfo *psInfo =
6064
0
        static_cast<GDALTransformerInfo *>(hTransformArg);
6065
0
    double adfGeoTransform[6];
6066
0
    double adfExtent[4];
6067
0
    int nXSize, nYSize;
6068
6069
0
    if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6070
0
                                 adfGeoTransform, &nXSize, &nYSize, adfExtent,
6071
0
                                 0) != CE_None)
6072
0
    {
6073
0
        CPLFree(pszWKT);
6074
0
        CSLDestroy(papszTO);
6075
0
        GDALDestroyGenImgProjTransformer(hTransformArg);
6076
0
        return nullptr;
6077
0
    }
6078
6079
0
    GDALDestroyGenImgProjTransformer(hTransformArg);
6080
0
    hTransformArg = nullptr;
6081
0
    poTmpDS.reset();
6082
6083
0
    if (bEPSG3857Adjust)
6084
0
    {
6085
0
        constexpr double SPHERICAL_RADIUS = 6378137.0;
6086
0
        constexpr double MAX_GM =
6087
0
            SPHERICAL_RADIUS * M_PI;  // 20037508.342789244
6088
0
        double maxNorthing = adfGeoTransform[3];
6089
0
        double minNorthing = adfGeoTransform[3] + adfGeoTransform[5] * nYSize;
6090
0
        bool bChanged = false;
6091
0
        if (maxNorthing > MAX_GM)
6092
0
        {
6093
0
            bChanged = true;
6094
0
            maxNorthing = MAX_GM;
6095
0
        }
6096
0
        if (minNorthing < -MAX_GM)
6097
0
        {
6098
0
            bChanged = true;
6099
0
            minNorthing = -MAX_GM;
6100
0
        }
6101
0
        if (bChanged)
6102
0
        {
6103
0
            adfGeoTransform[3] = maxNorthing;
6104
0
            nYSize =
6105
0
                int((maxNorthing - minNorthing) / (-adfGeoTransform[5]) + 0.5);
6106
0
            adfExtent[1] = maxNorthing + nYSize * adfGeoTransform[5];
6107
0
            adfExtent[3] = maxNorthing;
6108
0
        }
6109
0
    }
6110
6111
0
    double dfComputedRes = adfGeoTransform[1];
6112
0
    double dfPrevRes = 0.0;
6113
0
    double dfRes = 0.0;
6114
0
    int nZoomLevel = 0;  // Used after for.
6115
0
    const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6116
0
    if (pszZoomLevel)
6117
0
    {
6118
0
        nZoomLevel = atoi(pszZoomLevel);
6119
6120
0
        int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6121
0
        while ((1 << nMaxZoomLevelForThisTM) >
6122
0
                   INT_MAX / poTS->nTileXCountZoomLevel0 ||
6123
0
               (1 << nMaxZoomLevelForThisTM) >
6124
0
                   INT_MAX / poTS->nTileYCountZoomLevel0)
6125
0
        {
6126
0
            --nMaxZoomLevelForThisTM;
6127
0
        }
6128
6129
0
        if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6130
0
        {
6131
0
            CPLError(CE_Failure, CPLE_AppDefined,
6132
0
                     "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6133
0
                     pszZoomLevel, nMaxZoomLevelForThisTM);
6134
0
            CPLFree(pszWKT);
6135
0
            CSLDestroy(papszTO);
6136
0
            return nullptr;
6137
0
        }
6138
0
    }
6139
0
    else
6140
0
    {
6141
0
        for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6142
0
        {
6143
0
            dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6144
0
            if (dfComputedRes > dfRes ||
6145
0
                fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6146
0
                break;
6147
0
            dfPrevRes = dfRes;
6148
0
        }
6149
0
        if (nZoomLevel == MAX_ZOOM_LEVEL ||
6150
0
            (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6151
0
            (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6152
0
        {
6153
0
            CPLError(CE_Failure, CPLE_AppDefined,
6154
0
                     "Could not find an appropriate zoom level");
6155
0
            CPLFree(pszWKT);
6156
0
            CSLDestroy(papszTO);
6157
0
            return nullptr;
6158
0
        }
6159
6160
0
        if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6161
0
        {
6162
0
            const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6163
0
                papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6164
0
            if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6165
0
            {
6166
0
                nZoomLevel--;
6167
0
            }
6168
0
            else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6169
0
            {
6170
                /* do nothing */
6171
0
            }
6172
0
            else
6173
0
            {
6174
0
                if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6175
0
                    nZoomLevel--;
6176
0
            }
6177
0
        }
6178
0
    }
6179
6180
0
    dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6181
6182
0
    double dfMinX = adfExtent[0];
6183
0
    double dfMinY = adfExtent[1];
6184
0
    double dfMaxX = adfExtent[2];
6185
0
    double dfMaxY = adfExtent[3];
6186
6187
0
    nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6188
0
    nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6189
0
    adfGeoTransform[1] = dfRes;
6190
0
    adfGeoTransform[5] = -dfRes;
6191
6192
0
    const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6193
0
    int nTargetBands = nBands;
6194
    /* For grey level or RGB, if there's reprojection involved, add an alpha */
6195
    /* channel */
6196
0
    if (eDT == GDT_Byte &&
6197
0
        ((nBands == 1 &&
6198
0
          poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6199
0
         nBands == 3))
6200
0
    {
6201
0
        OGRSpatialReference oSrcSRS;
6202
0
        oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6203
0
        oSrcSRS.AutoIdentifyEPSG();
6204
0
        if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6205
0
            atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6206
0
        {
6207
0
            nTargetBands++;
6208
0
        }
6209
0
    }
6210
6211
0
    GDALResampleAlg eResampleAlg = GRA_Bilinear;
6212
0
    const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6213
0
    if (pszResampling)
6214
0
    {
6215
0
        for (size_t iAlg = 0;
6216
0
             iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6217
0
             iAlg++)
6218
0
        {
6219
0
            if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6220
0
            {
6221
0
                eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6222
0
                break;
6223
0
            }
6224
0
        }
6225
0
    }
6226
6227
0
    if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6228
0
        eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6229
0
    {
6230
0
        CPLError(
6231
0
            CE_Warning, CPLE_AppDefined,
6232
0
            "Input dataset has a color table, which will likely lead to "
6233
0
            "bad results when using a resampling method other than "
6234
0
            "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6235
0
            "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6236
0
    }
6237
6238
0
    auto poDS = std::make_unique<GDALGeoPackageDataset>();
6239
0
    if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6240
0
                       apszUpdatedOptions)))
6241
0
    {
6242
0
        CPLFree(pszWKT);
6243
0
        CSLDestroy(papszTO);
6244
0
        return nullptr;
6245
0
    }
6246
6247
    // Assign nodata values before the SetGeoTransform call.
6248
    // SetGeoTransform will trigger creation of the overview datasets for each
6249
    // zoom level and at that point the nodata value needs to be known.
6250
0
    int bHasNoData = FALSE;
6251
0
    double dfNoDataValue =
6252
0
        poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6253
0
    if (eDT != GDT_Byte && bHasNoData)
6254
0
    {
6255
0
        poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6256
0
    }
6257
6258
0
    poDS->SetGeoTransform(adfGeoTransform);
6259
0
    poDS->SetProjection(pszWKT);
6260
0
    CPLFree(pszWKT);
6261
0
    pszWKT = nullptr;
6262
0
    if (nTargetBands == 1 && nBands == 1 &&
6263
0
        poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6264
0
    {
6265
0
        poDS->GetRasterBand(1)->SetColorTable(
6266
0
            poSrcDS->GetRasterBand(1)->GetColorTable());
6267
0
    }
6268
6269
0
    hTransformArg =
6270
0
        GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6271
0
    CSLDestroy(papszTO);
6272
0
    if (hTransformArg == nullptr)
6273
0
    {
6274
0
        return nullptr;
6275
0
    }
6276
6277
0
    poDS->SetMetadata(poSrcDS->GetMetadata());
6278
6279
    /* -------------------------------------------------------------------- */
6280
    /*      Warp the transformer with a linear approximator                 */
6281
    /* -------------------------------------------------------------------- */
6282
0
    hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6283
0
                                                hTransformArg, 0.125);
6284
0
    GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6285
6286
    /* -------------------------------------------------------------------- */
6287
    /*      Setup warp options.                                             */
6288
    /* -------------------------------------------------------------------- */
6289
0
    GDALWarpOptions *psWO = GDALCreateWarpOptions();
6290
6291
0
    psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6292
0
    psWO->papszWarpOptions =
6293
0
        CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6294
0
    if (bHasNoData)
6295
0
    {
6296
0
        if (dfNoDataValue == 0.0)
6297
0
        {
6298
            // Do not initialize in the case where nodata != 0, since we
6299
            // want the GeoPackage driver to return empty tiles at the nodata
6300
            // value instead of 0 as GDAL core would
6301
0
            psWO->papszWarpOptions =
6302
0
                CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6303
0
        }
6304
6305
0
        psWO->padfSrcNoDataReal =
6306
0
            static_cast<double *>(CPLMalloc(sizeof(double)));
6307
0
        psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6308
6309
0
        psWO->padfDstNoDataReal =
6310
0
            static_cast<double *>(CPLMalloc(sizeof(double)));
6311
0
        psWO->padfDstNoDataReal[0] = dfNoDataValue;
6312
0
    }
6313
0
    psWO->eWorkingDataType = eDT;
6314
0
    psWO->eResampleAlg = eResampleAlg;
6315
6316
0
    psWO->hSrcDS = poSrcDS;
6317
0
    psWO->hDstDS = poDS.get();
6318
6319
0
    psWO->pfnTransformer = GDALApproxTransform;
6320
0
    psWO->pTransformerArg = hTransformArg;
6321
6322
0
    psWO->pfnProgress = pfnProgress;
6323
0
    psWO->pProgressArg = pProgressData;
6324
6325
    /* -------------------------------------------------------------------- */
6326
    /*      Setup band mapping.                                             */
6327
    /* -------------------------------------------------------------------- */
6328
6329
0
    if (nBands == 2 || nBands == 4)
6330
0
        psWO->nBandCount = nBands - 1;
6331
0
    else
6332
0
        psWO->nBandCount = nBands;
6333
6334
0
    psWO->panSrcBands =
6335
0
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6336
0
    psWO->panDstBands =
6337
0
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6338
6339
0
    for (int i = 0; i < psWO->nBandCount; i++)
6340
0
    {
6341
0
        psWO->panSrcBands[i] = i + 1;
6342
0
        psWO->panDstBands[i] = i + 1;
6343
0
    }
6344
6345
0
    if (nBands == 2 || nBands == 4)
6346
0
    {
6347
0
        psWO->nSrcAlphaBand = nBands;
6348
0
    }
6349
0
    if (nTargetBands == 2 || nTargetBands == 4)
6350
0
    {
6351
0
        psWO->nDstAlphaBand = nTargetBands;
6352
0
    }
6353
6354
    /* -------------------------------------------------------------------- */
6355
    /*      Initialize and execute the warp.                                */
6356
    /* -------------------------------------------------------------------- */
6357
0
    GDALWarpOperation oWO;
6358
6359
0
    CPLErr eErr = oWO.Initialize(psWO);
6360
0
    if (eErr == CE_None)
6361
0
    {
6362
        /*if( bMulti )
6363
            eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6364
        else*/
6365
0
        eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6366
0
    }
6367
0
    if (eErr != CE_None)
6368
0
    {
6369
0
        poDS.reset();
6370
0
    }
6371
6372
0
    GDALDestroyTransformer(hTransformArg);
6373
0
    GDALDestroyWarpOptions(psWO);
6374
6375
0
    if (poDS)
6376
0
        poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6377
6378
0
    return poDS.release();
6379
0
}
6380
6381
/************************************************************************/
6382
/*                        ParseCompressionOptions()                     */
6383
/************************************************************************/
6384
6385
void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6386
10
{
6387
10
    const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6388
10
    if (pszZLevel)
6389
0
        m_nZLevel = atoi(pszZLevel);
6390
6391
10
    const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6392
10
    if (pszQuality)
6393
0
        m_nQuality = atoi(pszQuality);
6394
6395
10
    const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6396
10
    if (pszDither)
6397
0
        m_bDither = CPLTestBool(pszDither);
6398
10
}
6399
6400
/************************************************************************/
6401
/*                          RegisterWebPExtension()                     */
6402
/************************************************************************/
6403
6404
bool GDALGeoPackageDataset::RegisterWebPExtension()
6405
0
{
6406
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6407
0
        return false;
6408
6409
0
    char *pszSQL = sqlite3_mprintf(
6410
0
        "INSERT INTO gpkg_extensions "
6411
0
        "(table_name, column_name, extension_name, definition, scope) "
6412
0
        "VALUES "
6413
0
        "('%q', 'tile_data', 'gpkg_webp', "
6414
0
        "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6415
0
        "'read-write')",
6416
0
        m_osRasterTable.c_str());
6417
0
    const OGRErr eErr = SQLCommand(hDB, pszSQL);
6418
0
    sqlite3_free(pszSQL);
6419
6420
0
    return OGRERR_NONE == eErr;
6421
0
}
6422
6423
/************************************************************************/
6424
/*                       RegisterZoomOtherExtension()                   */
6425
/************************************************************************/
6426
6427
bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6428
0
{
6429
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6430
0
        return false;
6431
6432
0
    char *pszSQL = sqlite3_mprintf(
6433
0
        "INSERT INTO gpkg_extensions "
6434
0
        "(table_name, column_name, extension_name, definition, scope) "
6435
0
        "VALUES "
6436
0
        "('%q', 'tile_data', 'gpkg_zoom_other', "
6437
0
        "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6438
0
        "'read-write')",
6439
0
        m_osRasterTable.c_str());
6440
0
    const OGRErr eErr = SQLCommand(hDB, pszSQL);
6441
0
    sqlite3_free(pszSQL);
6442
0
    return OGRERR_NONE == eErr;
6443
0
}
6444
6445
/************************************************************************/
6446
/*                              GetLayer()                              */
6447
/************************************************************************/
6448
6449
OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer)
6450
6451
1.49M
{
6452
1.49M
    if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6453
0
        return nullptr;
6454
1.49M
    else
6455
1.49M
        return m_apoLayers[iLayer].get();
6456
1.49M
}
6457
6458
/************************************************************************/
6459
/*                           LaunderName()                              */
6460
/************************************************************************/
6461
6462
/** Launder identifiers (table, column names) according to guidance at
6463
 * https://www.geopackage.org/guidance/getting-started.html:
6464
 * "For maximum interoperability, start your database identifiers (table names,
6465
 * column names, etc.) with a lowercase character and only use lowercase
6466
 * characters, numbers 0-9, and underscores (_)."
6467
 */
6468
6469
/* static */
6470
std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6471
0
{
6472
0
    char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6473
0
    const std::string osStrASCII(pszASCII);
6474
0
    CPLFree(pszASCII);
6475
6476
0
    std::string osRet;
6477
0
    osRet.reserve(osStrASCII.size());
6478
6479
0
    for (size_t i = 0; i < osStrASCII.size(); ++i)
6480
0
    {
6481
0
        if (osRet.empty())
6482
0
        {
6483
0
            if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6484
0
            {
6485
0
                osRet += (osStrASCII[i] - 'A' + 'a');
6486
0
            }
6487
0
            else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6488
0
            {
6489
0
                osRet += osStrASCII[i];
6490
0
            }
6491
0
            else
6492
0
            {
6493
0
                continue;
6494
0
            }
6495
0
        }
6496
0
        else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6497
0
        {
6498
0
            osRet += (osStrASCII[i] - 'A' + 'a');
6499
0
        }
6500
0
        else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6501
0
                 (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6502
0
                 osStrASCII[i] == '_')
6503
0
        {
6504
0
            osRet += osStrASCII[i];
6505
0
        }
6506
0
        else
6507
0
        {
6508
0
            osRet += '_';
6509
0
        }
6510
0
    }
6511
6512
0
    if (osRet.empty() && !osStrASCII.empty())
6513
0
        return LaunderName(std::string("x").append(osStrASCII));
6514
6515
0
    if (osRet != osStr)
6516
0
    {
6517
0
        CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6518
0
                 osRet.c_str());
6519
0
    }
6520
6521
0
    return osRet;
6522
0
}
6523
6524
/************************************************************************/
6525
/*                          ICreateLayer()                              */
6526
/************************************************************************/
6527
6528
OGRLayer *
6529
GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6530
                                    const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6531
                                    CSLConstList papszOptions)
6532
1.74k
{
6533
    /* -------------------------------------------------------------------- */
6534
    /*      Verify we are in update mode.                                   */
6535
    /* -------------------------------------------------------------------- */
6536
1.74k
    if (!GetUpdate())
6537
0
    {
6538
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
6539
0
                 "Data source %s opened read-only.\n"
6540
0
                 "New layer %s cannot be created.\n",
6541
0
                 m_pszFilename, pszLayerName);
6542
6543
0
        return nullptr;
6544
0
    }
6545
6546
1.74k
    const bool bLaunder =
6547
1.74k
        CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6548
1.74k
    const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6549
1.74k
                                           : std::string(pszLayerName));
6550
6551
1.74k
    const auto eGType =
6552
1.74k
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6553
1.74k
    const auto poSpatialRef =
6554
1.74k
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6555
6556
1.74k
    if (!m_bHasGPKGGeometryColumns)
6557
0
    {
6558
0
        if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6559
0
        {
6560
0
            return nullptr;
6561
0
        }
6562
0
        m_bHasGPKGGeometryColumns = true;
6563
0
    }
6564
6565
    // Check identifier unicity
6566
1.74k
    const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6567
1.74k
    if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6568
0
        pszIdentifier = nullptr;
6569
1.74k
    if (pszIdentifier != nullptr)
6570
0
    {
6571
0
        for (auto &poLayer : m_apoLayers)
6572
0
        {
6573
0
            const char *pszOtherIdentifier =
6574
0
                poLayer->GetMetadataItem("IDENTIFIER");
6575
0
            if (pszOtherIdentifier == nullptr)
6576
0
                pszOtherIdentifier = poLayer->GetName();
6577
0
            if (pszOtherIdentifier != nullptr &&
6578
0
                EQUAL(pszOtherIdentifier, pszIdentifier) &&
6579
0
                !EQUAL(poLayer->GetName(), osTableName.c_str()))
6580
0
            {
6581
0
                CPLError(CE_Failure, CPLE_AppDefined,
6582
0
                         "Identifier %s is already used by table %s",
6583
0
                         pszIdentifier, poLayer->GetName());
6584
0
                return nullptr;
6585
0
            }
6586
0
        }
6587
6588
        // In case there would be table in gpkg_contents not listed as a
6589
        // vector layer
6590
0
        char *pszSQL = sqlite3_mprintf(
6591
0
            "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6592
0
            "LIMIT 2",
6593
0
            pszIdentifier);
6594
0
        auto oResult = SQLQuery(hDB, pszSQL);
6595
0
        sqlite3_free(pszSQL);
6596
0
        if (oResult && oResult->RowCount() > 0 &&
6597
0
            oResult->GetValue(0, 0) != nullptr &&
6598
0
            !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6599
0
        {
6600
0
            CPLError(CE_Failure, CPLE_AppDefined,
6601
0
                     "Identifier %s is already used by table %s", pszIdentifier,
6602
0
                     oResult->GetValue(0, 0));
6603
0
            return nullptr;
6604
0
        }
6605
0
    }
6606
6607
    /* Read GEOMETRY_NAME option */
6608
1.74k
    const char *pszGeomColumnName =
6609
1.74k
        CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6610
1.74k
    if (pszGeomColumnName == nullptr) /* deprecated name */
6611
1.43k
        pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6612
1.74k
    if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6613
258
    {
6614
258
        pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6615
258
        if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6616
258
            pszGeomColumnName = nullptr;
6617
258
    }
6618
1.74k
    if (pszGeomColumnName == nullptr)
6619
1.43k
        pszGeomColumnName = "geom";
6620
1.74k
    const bool bGeomNullable =
6621
1.74k
        CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6622
6623
    /* Read FID option */
6624
1.74k
    const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6625
1.74k
    if (pszFIDColumnName == nullptr)
6626
1.74k
        pszFIDColumnName = "fid";
6627
6628
1.74k
    if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6629
1.74k
    {
6630
1.74k
        if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6631
0
        {
6632
0
            CPLError(CE_Failure, CPLE_AppDefined,
6633
0
                     "The primary key (%s) name may not contain special "
6634
0
                     "characters or spaces",
6635
0
                     pszFIDColumnName);
6636
0
            return nullptr;
6637
0
        }
6638
6639
        /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6640
         */
6641
1.74k
        if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6642
0
        {
6643
0
            CPLError(CE_Failure, CPLE_AppDefined,
6644
0
                     "The layer name may not begin with 'gpkg' as it is a "
6645
0
                     "reserved geopackage prefix");
6646
0
            return nullptr;
6647
0
        }
6648
6649
        /* Preemptively try and avoid sqlite3 syntax errors due to  */
6650
        /* illegal characters. */
6651
1.74k
        if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6652
1.74k
            0)
6653
66
        {
6654
66
            CPLError(
6655
66
                CE_Failure, CPLE_AppDefined,
6656
66
                "The layer name may not contain special characters or spaces");
6657
66
            return nullptr;
6658
66
        }
6659
1.74k
    }
6660
6661
    /* Check for any existing layers that already use this name */
6662
19.7k
    for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6663
18.0k
         iLayer++)
6664
18.0k
    {
6665
18.0k
        if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6666
39
        {
6667
39
            const char *pszOverwrite =
6668
39
                CSLFetchNameValue(papszOptions, "OVERWRITE");
6669
39
            if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6670
0
            {
6671
0
                DeleteLayer(iLayer);
6672
0
            }
6673
39
            else
6674
39
            {
6675
39
                CPLError(CE_Failure, CPLE_AppDefined,
6676
39
                         "Layer %s already exists, CreateLayer failed.\n"
6677
39
                         "Use the layer creation option OVERWRITE=YES to "
6678
39
                         "replace it.",
6679
39
                         osTableName.c_str());
6680
39
                return nullptr;
6681
39
            }
6682
39
        }
6683
18.0k
    }
6684
6685
1.64k
    if (m_apoLayers.size() == 1)
6686
215
    {
6687
        // Async RTree building doesn't play well with multiple layer:
6688
        // SQLite3 locks being hold for a long time, random failed commits,
6689
        // etc.
6690
215
        m_apoLayers[0]->FinishOrDisableThreadedRTree();
6691
215
    }
6692
6693
    /* Create a blank layer. */
6694
1.64k
    auto poLayer =
6695
1.64k
        std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6696
6697
1.64k
    OGRSpatialReference *poSRS = nullptr;
6698
1.64k
    if (poSpatialRef)
6699
150
    {
6700
150
        poSRS = poSpatialRef->Clone();
6701
150
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6702
150
    }
6703
1.64k
    poLayer->SetCreationParameters(
6704
1.64k
        eGType,
6705
1.64k
        bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6706
1.64k
        bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6707
1.64k
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6708
1.64k
                           : OGRGeomCoordinatePrecision(),
6709
1.64k
        CPLTestBool(
6710
1.64k
            CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6711
1.64k
        CPLTestBool(CSLFetchNameValueDef(
6712
1.64k
            papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6713
1.64k
        bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6714
1.64k
        pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6715
1.64k
    if (poSRS)
6716
150
    {
6717
150
        poSRS->Release();
6718
150
    }
6719
6720
1.64k
    poLayer->SetLaunder(bLaunder);
6721
6722
    /* Should we create a spatial index ? */
6723
1.64k
    const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6724
1.64k
    int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6725
1.64k
    if (eGType != wkbNone && bCreateSpatialIndex)
6726
475
    {
6727
475
        poLayer->SetDeferredSpatialIndexCreation(true);
6728
475
    }
6729
6730
1.64k
    poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6731
1.64k
    poLayer->SetTruncateFieldsFlag(
6732
1.64k
        CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6733
1.64k
    if (eGType == wkbNone)
6734
1.08k
    {
6735
1.08k
        const char *pszASpatialVariant = CSLFetchNameValueDef(
6736
1.08k
            papszOptions, "ASPATIAL_VARIANT",
6737
1.08k
            m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6738
1.08k
                ? "NOT_REGISTERED"
6739
1.08k
                : "GPKG_ATTRIBUTES");
6740
1.08k
        GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6741
1.08k
        if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6742
1.08k
            eASpatialVariant = GPKG_ATTRIBUTES;
6743
0
        else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6744
0
        {
6745
0
            CPLError(CE_Failure, CPLE_NotSupported,
6746
0
                     "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6747
0
            return nullptr;
6748
0
        }
6749
0
        else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6750
0
            eASpatialVariant = NOT_REGISTERED;
6751
0
        else
6752
0
        {
6753
0
            CPLError(CE_Failure, CPLE_NotSupported,
6754
0
                     "Unsupported value for ASPATIAL_VARIANT: %s",
6755
0
                     pszASpatialVariant);
6756
0
            return nullptr;
6757
0
        }
6758
1.08k
        poLayer->SetASpatialVariant(eASpatialVariant);
6759
1.08k
    }
6760
6761
1.64k
    const char *pszDateTimePrecision =
6762
1.64k
        CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6763
1.64k
    if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6764
0
    {
6765
0
        poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6766
0
    }
6767
1.64k
    else if (EQUAL(pszDateTimePrecision, "SECOND"))
6768
0
    {
6769
0
        if (m_nUserVersion < GPKG_1_4_VERSION)
6770
0
            CPLError(
6771
0
                CE_Warning, CPLE_AppDefined,
6772
0
                "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6773
0
        poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6774
0
    }
6775
1.64k
    else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6776
0
    {
6777
0
        if (m_nUserVersion < GPKG_1_4_VERSION)
6778
0
            CPLError(
6779
0
                CE_Warning, CPLE_AppDefined,
6780
0
                "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6781
0
        poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6782
0
    }
6783
1.64k
    else if (EQUAL(pszDateTimePrecision, "AUTO"))
6784
1.64k
    {
6785
1.64k
        if (m_nUserVersion < GPKG_1_4_VERSION)
6786
0
            poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6787
1.64k
    }
6788
0
    else
6789
0
    {
6790
0
        CPLError(CE_Failure, CPLE_NotSupported,
6791
0
                 "Unsupported value for DATETIME_PRECISION: %s",
6792
0
                 pszDateTimePrecision);
6793
0
        return nullptr;
6794
0
    }
6795
6796
    // If there was an ogr_empty_table table, we can remove it
6797
    // But do it at dataset closing, otherwise locking performance issues
6798
    // can arise (probably when transactions are used).
6799
1.64k
    m_bRemoveOGREmptyTable = true;
6800
6801
1.64k
    m_apoLayers.emplace_back(std::move(poLayer));
6802
1.64k
    return m_apoLayers.back().get();
6803
1.64k
}
6804
6805
/************************************************************************/
6806
/*                          FindLayerIndex()                            */
6807
/************************************************************************/
6808
6809
int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6810
6811
0
{
6812
0
    for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6813
0
         iLayer++)
6814
0
    {
6815
0
        if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6816
0
            return iLayer;
6817
0
    }
6818
0
    return -1;
6819
0
}
6820
6821
/************************************************************************/
6822
/*                       DeleteLayerCommon()                            */
6823
/************************************************************************/
6824
6825
OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6826
0
{
6827
    // Temporary remove foreign key checks
6828
0
    const GPKGTemporaryForeignKeyCheckDisabler
6829
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
6830
6831
0
    char *pszSQL = sqlite3_mprintf(
6832
0
        "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6833
0
        pszLayerName);
6834
0
    OGRErr eErr = SQLCommand(hDB, pszSQL);
6835
0
    sqlite3_free(pszSQL);
6836
6837
0
    if (eErr == OGRERR_NONE && HasExtensionsTable())
6838
0
    {
6839
0
        pszSQL = sqlite3_mprintf(
6840
0
            "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6841
0
            pszLayerName);
6842
0
        eErr = SQLCommand(hDB, pszSQL);
6843
0
        sqlite3_free(pszSQL);
6844
0
    }
6845
6846
0
    if (eErr == OGRERR_NONE && HasMetadataTables())
6847
0
    {
6848
        // Delete from gpkg_metadata metadata records that are only referenced
6849
        // by the table we are about to drop
6850
0
        pszSQL = sqlite3_mprintf(
6851
0
            "DELETE FROM gpkg_metadata WHERE id IN ("
6852
0
            "SELECT DISTINCT md_file_id FROM "
6853
0
            "gpkg_metadata_reference WHERE "
6854
0
            "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6855
0
            "AND id NOT IN ("
6856
0
            "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6857
0
            "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6858
0
            "gpkg_metadata_reference WHERE "
6859
0
            "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6860
0
            "AND lower(table_name) <> lower('%q'))",
6861
0
            pszLayerName, pszLayerName, pszLayerName);
6862
0
        eErr = SQLCommand(hDB, pszSQL);
6863
0
        sqlite3_free(pszSQL);
6864
6865
0
        if (eErr == OGRERR_NONE)
6866
0
        {
6867
0
            pszSQL =
6868
0
                sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6869
0
                                "lower(table_name) = lower('%q')",
6870
0
                                pszLayerName);
6871
0
            eErr = SQLCommand(hDB, pszSQL);
6872
0
            sqlite3_free(pszSQL);
6873
0
        }
6874
0
    }
6875
6876
0
    if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6877
0
    {
6878
        // Remove reference to potential corresponding mapping table in
6879
        // gpkg_extensions
6880
0
        pszSQL = sqlite3_mprintf(
6881
0
            "DELETE FROM gpkg_extensions WHERE "
6882
0
            "extension_name IN ('related_tables', "
6883
0
            "'gpkg_related_tables') AND lower(table_name) = "
6884
0
            "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6885
0
            "lower(base_table_name) = lower('%q') OR "
6886
0
            "lower(related_table_name) = lower('%q') OR "
6887
0
            "lower(mapping_table_name) = lower('%q'))",
6888
0
            pszLayerName, pszLayerName, pszLayerName);
6889
0
        eErr = SQLCommand(hDB, pszSQL);
6890
0
        sqlite3_free(pszSQL);
6891
6892
0
        if (eErr == OGRERR_NONE)
6893
0
        {
6894
            // Remove reference to potential corresponding mapping table in
6895
            // gpkgext_relations
6896
0
            pszSQL =
6897
0
                sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6898
0
                                "lower(base_table_name) = lower('%q') OR "
6899
0
                                "lower(related_table_name) = lower('%q') OR "
6900
0
                                "lower(mapping_table_name) = lower('%q')",
6901
0
                                pszLayerName, pszLayerName, pszLayerName);
6902
0
            eErr = SQLCommand(hDB, pszSQL);
6903
0
            sqlite3_free(pszSQL);
6904
0
        }
6905
6906
0
        if (eErr == OGRERR_NONE && HasExtensionsTable())
6907
0
        {
6908
            // If there is no longer any mapping table, then completely
6909
            // remove any reference to the extension in gpkg_extensions
6910
            // as mandated per the related table specification.
6911
0
            OGRErr err;
6912
0
            if (SQLGetInteger(hDB,
6913
0
                              "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6914
0
                              "extension_name IN ('related_tables', "
6915
0
                              "'gpkg_related_tables') AND "
6916
0
                              "lower(table_name) != 'gpkgext_relations'",
6917
0
                              &err) == 0)
6918
0
            {
6919
0
                eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6920
0
                                       "extension_name IN ('related_tables', "
6921
0
                                       "'gpkg_related_tables')");
6922
0
            }
6923
6924
0
            ClearCachedRelationships();
6925
0
        }
6926
0
    }
6927
6928
0
    if (eErr == OGRERR_NONE)
6929
0
    {
6930
0
        pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6931
0
        eErr = SQLCommand(hDB, pszSQL);
6932
0
        sqlite3_free(pszSQL);
6933
0
    }
6934
6935
    // Check foreign key integrity
6936
0
    if (eErr == OGRERR_NONE)
6937
0
    {
6938
0
        eErr = PragmaCheck("foreign_key_check", "", 0);
6939
0
    }
6940
6941
0
    return eErr;
6942
0
}
6943
6944
/************************************************************************/
6945
/*                            DeleteLayer()                             */
6946
/************************************************************************/
6947
6948
OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6949
0
{
6950
0
    if (!GetUpdate() || iLayer < 0 ||
6951
0
        iLayer >= static_cast<int>(m_apoLayers.size()))
6952
0
        return OGRERR_FAILURE;
6953
6954
0
    m_apoLayers[iLayer]->ResetReading();
6955
0
    m_apoLayers[iLayer]->SyncToDisk();
6956
6957
0
    CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6958
6959
0
    CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6960
6961
    // Temporary remove foreign key checks
6962
0
    const GPKGTemporaryForeignKeyCheckDisabler
6963
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
6964
6965
0
    OGRErr eErr = SoftStartTransaction();
6966
6967
0
    if (eErr == OGRERR_NONE)
6968
0
    {
6969
0
        if (m_apoLayers[iLayer]->HasSpatialIndex())
6970
0
            m_apoLayers[iLayer]->DropSpatialIndex();
6971
6972
0
        char *pszSQL =
6973
0
            sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6974
0
                            "lower(table_name) = lower('%q')",
6975
0
                            osLayerName.c_str());
6976
0
        eErr = SQLCommand(hDB, pszSQL);
6977
0
        sqlite3_free(pszSQL);
6978
0
    }
6979
6980
0
    if (eErr == OGRERR_NONE && HasDataColumnsTable())
6981
0
    {
6982
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6983
0
                                       "lower(table_name) = lower('%q')",
6984
0
                                       osLayerName.c_str());
6985
0
        eErr = SQLCommand(hDB, pszSQL);
6986
0
        sqlite3_free(pszSQL);
6987
0
    }
6988
6989
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
6990
0
    if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6991
0
    {
6992
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6993
0
                                       "lower(table_name) = lower('%q')",
6994
0
                                       osLayerName.c_str());
6995
0
        eErr = SQLCommand(hDB, pszSQL);
6996
0
        sqlite3_free(pszSQL);
6997
0
    }
6998
0
#endif
6999
7000
0
    if (eErr == OGRERR_NONE)
7001
0
    {
7002
0
        eErr = DeleteLayerCommon(osLayerName.c_str());
7003
0
    }
7004
7005
0
    if (eErr == OGRERR_NONE)
7006
0
    {
7007
0
        eErr = SoftCommitTransaction();
7008
0
        if (eErr == OGRERR_NONE)
7009
0
        {
7010
            /* Delete the layer object */
7011
0
            m_apoLayers.erase(m_apoLayers.begin() + iLayer);
7012
0
        }
7013
0
    }
7014
0
    else
7015
0
    {
7016
0
        SoftRollbackTransaction();
7017
0
    }
7018
7019
0
    return eErr;
7020
0
}
7021
7022
/************************************************************************/
7023
/*                       DeleteRasterLayer()                            */
7024
/************************************************************************/
7025
7026
OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7027
0
{
7028
    // Temporary remove foreign key checks
7029
0
    const GPKGTemporaryForeignKeyCheckDisabler
7030
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
7031
7032
0
    OGRErr eErr = SoftStartTransaction();
7033
7034
0
    if (eErr == OGRERR_NONE)
7035
0
    {
7036
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7037
0
                                       "lower(table_name) = lower('%q')",
7038
0
                                       pszLayerName);
7039
0
        eErr = SQLCommand(hDB, pszSQL);
7040
0
        sqlite3_free(pszSQL);
7041
0
    }
7042
7043
0
    if (eErr == OGRERR_NONE)
7044
0
    {
7045
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7046
0
                                       "lower(table_name) = lower('%q')",
7047
0
                                       pszLayerName);
7048
0
        eErr = SQLCommand(hDB, pszSQL);
7049
0
        sqlite3_free(pszSQL);
7050
0
    }
7051
7052
0
    if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7053
0
    {
7054
0
        char *pszSQL =
7055
0
            sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7056
0
                            "WHERE lower(tile_matrix_set_name) = lower('%q')",
7057
0
                            pszLayerName);
7058
0
        eErr = SQLCommand(hDB, pszSQL);
7059
0
        sqlite3_free(pszSQL);
7060
7061
0
        if (eErr == OGRERR_NONE)
7062
0
        {
7063
0
            pszSQL =
7064
0
                sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7065
0
                                "WHERE lower(tpudt_name) = lower('%q')",
7066
0
                                pszLayerName);
7067
0
            eErr = SQLCommand(hDB, pszSQL);
7068
0
            sqlite3_free(pszSQL);
7069
0
        }
7070
0
    }
7071
7072
0
    if (eErr == OGRERR_NONE)
7073
0
    {
7074
0
        eErr = DeleteLayerCommon(pszLayerName);
7075
0
    }
7076
7077
0
    if (eErr == OGRERR_NONE)
7078
0
    {
7079
0
        eErr = SoftCommitTransaction();
7080
0
    }
7081
0
    else
7082
0
    {
7083
0
        SoftRollbackTransaction();
7084
0
    }
7085
7086
0
    return eErr;
7087
0
}
7088
7089
/************************************************************************/
7090
/*                    DeleteVectorOrRasterLayer()                       */
7091
/************************************************************************/
7092
7093
bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7094
0
{
7095
7096
0
    int idx = FindLayerIndex(pszLayerName);
7097
0
    if (idx >= 0)
7098
0
    {
7099
0
        DeleteLayer(idx);
7100
0
        return true;
7101
0
    }
7102
7103
0
    char *pszSQL =
7104
0
        sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7105
0
                        "lower(table_name) = lower('%q') "
7106
0
                        "AND data_type IN ('tiles', '2d-gridded-coverage')",
7107
0
                        pszLayerName);
7108
0
    bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7109
0
    sqlite3_free(pszSQL);
7110
0
    if (bIsRasterTable)
7111
0
    {
7112
0
        DeleteRasterLayer(pszLayerName);
7113
0
        return true;
7114
0
    }
7115
0
    return false;
7116
0
}
7117
7118
bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7119
    const char *pszLayerName, const char *pszNewLayerName)
7120
0
{
7121
0
    int idx = FindLayerIndex(pszLayerName);
7122
0
    if (idx >= 0)
7123
0
    {
7124
0
        m_apoLayers[idx]->Rename(pszNewLayerName);
7125
0
        return true;
7126
0
    }
7127
7128
0
    char *pszSQL =
7129
0
        sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7130
0
                        "lower(table_name) = lower('%q') "
7131
0
                        "AND data_type IN ('tiles', '2d-gridded-coverage')",
7132
0
                        pszLayerName);
7133
0
    const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7134
0
    sqlite3_free(pszSQL);
7135
7136
0
    if (bIsRasterTable)
7137
0
    {
7138
0
        return RenameRasterLayer(pszLayerName, pszNewLayerName);
7139
0
    }
7140
7141
0
    return false;
7142
0
}
7143
7144
bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7145
                                              const char *pszNewLayerName)
7146
0
{
7147
0
    std::string osSQL;
7148
7149
0
    char *pszSQL = sqlite3_mprintf(
7150
0
        "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7151
0
        "AND type IN ('table', 'view')",
7152
0
        pszNewLayerName);
7153
0
    const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7154
0
    sqlite3_free(pszSQL);
7155
0
    if (bAlreadyExists)
7156
0
    {
7157
0
        CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7158
0
                 pszNewLayerName);
7159
0
        return false;
7160
0
    }
7161
7162
    // Temporary remove foreign key checks
7163
0
    const GPKGTemporaryForeignKeyCheckDisabler
7164
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
7165
7166
0
    if (SoftStartTransaction() != OGRERR_NONE)
7167
0
    {
7168
0
        return false;
7169
0
    }
7170
7171
0
    pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7172
0
                             "lower(table_name) = lower('%q');",
7173
0
                             pszNewLayerName, pszLayerName);
7174
0
    osSQL = pszSQL;
7175
0
    sqlite3_free(pszSQL);
7176
7177
0
    pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7178
0
                             "lower(identifier) = lower('%q');",
7179
0
                             pszNewLayerName, pszLayerName);
7180
0
    osSQL += pszSQL;
7181
0
    sqlite3_free(pszSQL);
7182
7183
0
    pszSQL =
7184
0
        sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7185
0
                        "lower(table_name) = lower('%q');",
7186
0
                        pszNewLayerName, pszLayerName);
7187
0
    osSQL += pszSQL;
7188
0
    sqlite3_free(pszSQL);
7189
7190
0
    pszSQL = sqlite3_mprintf(
7191
0
        "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7192
0
        "lower(table_name) = lower('%q');",
7193
0
        pszNewLayerName, pszLayerName);
7194
0
    osSQL += pszSQL;
7195
0
    sqlite3_free(pszSQL);
7196
7197
0
    if (HasGriddedCoverageAncillaryTable())
7198
0
    {
7199
0
        pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7200
0
                                 "SET tile_matrix_set_name = '%q' WHERE "
7201
0
                                 "lower(tile_matrix_set_name) = lower('%q');",
7202
0
                                 pszNewLayerName, pszLayerName);
7203
0
        osSQL += pszSQL;
7204
0
        sqlite3_free(pszSQL);
7205
7206
0
        pszSQL = sqlite3_mprintf(
7207
0
            "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7208
0
            "lower(tpudt_name) = lower('%q');",
7209
0
            pszNewLayerName, pszLayerName);
7210
0
        osSQL += pszSQL;
7211
0
        sqlite3_free(pszSQL);
7212
0
    }
7213
7214
0
    if (HasExtensionsTable())
7215
0
    {
7216
0
        pszSQL = sqlite3_mprintf(
7217
0
            "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7218
0
            "lower(table_name) = lower('%q');",
7219
0
            pszNewLayerName, pszLayerName);
7220
0
        osSQL += pszSQL;
7221
0
        sqlite3_free(pszSQL);
7222
0
    }
7223
7224
0
    if (HasMetadataTables())
7225
0
    {
7226
0
        pszSQL = sqlite3_mprintf(
7227
0
            "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7228
0
            "lower(table_name) = lower('%q');",
7229
0
            pszNewLayerName, pszLayerName);
7230
0
        osSQL += pszSQL;
7231
0
        sqlite3_free(pszSQL);
7232
0
    }
7233
7234
0
    if (HasDataColumnsTable())
7235
0
    {
7236
0
        pszSQL = sqlite3_mprintf(
7237
0
            "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7238
0
            "lower(table_name) = lower('%q');",
7239
0
            pszNewLayerName, pszLayerName);
7240
0
        osSQL += pszSQL;
7241
0
        sqlite3_free(pszSQL);
7242
0
    }
7243
7244
0
    if (HasQGISLayerStyles())
7245
0
    {
7246
        // Update QGIS styles
7247
0
        pszSQL =
7248
0
            sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7249
0
                            "lower(f_table_name) = lower('%q');",
7250
0
                            pszNewLayerName, pszLayerName);
7251
0
        osSQL += pszSQL;
7252
0
        sqlite3_free(pszSQL);
7253
0
    }
7254
7255
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
7256
0
    if (m_bHasGPKGOGRContents)
7257
0
    {
7258
0
        pszSQL = sqlite3_mprintf(
7259
0
            "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7260
0
            "lower(table_name) = lower('%q');",
7261
0
            pszNewLayerName, pszLayerName);
7262
0
        osSQL += pszSQL;
7263
0
        sqlite3_free(pszSQL);
7264
0
    }
7265
0
#endif
7266
7267
0
    if (HasGpkgextRelationsTable())
7268
0
    {
7269
0
        pszSQL = sqlite3_mprintf(
7270
0
            "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7271
0
            "lower(base_table_name) = lower('%q');",
7272
0
            pszNewLayerName, pszLayerName);
7273
0
        osSQL += pszSQL;
7274
0
        sqlite3_free(pszSQL);
7275
7276
0
        pszSQL = sqlite3_mprintf(
7277
0
            "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7278
0
            "lower(related_table_name) = lower('%q');",
7279
0
            pszNewLayerName, pszLayerName);
7280
0
        osSQL += pszSQL;
7281
0
        sqlite3_free(pszSQL);
7282
7283
0
        pszSQL = sqlite3_mprintf(
7284
0
            "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7285
0
            "lower(mapping_table_name) = lower('%q');",
7286
0
            pszNewLayerName, pszLayerName);
7287
0
        osSQL += pszSQL;
7288
0
        sqlite3_free(pszSQL);
7289
0
    }
7290
7291
    // Drop all triggers for the layer
7292
0
    pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7293
0
                             "'trigger' AND tbl_name = '%q'",
7294
0
                             pszLayerName);
7295
0
    auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7296
0
    sqlite3_free(pszSQL);
7297
0
    if (oTriggerResult)
7298
0
    {
7299
0
        for (int i = 0; i < oTriggerResult->RowCount(); i++)
7300
0
        {
7301
0
            const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7302
0
            pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7303
0
                                     pszTriggerName);
7304
0
            osSQL += pszSQL;
7305
0
            sqlite3_free(pszSQL);
7306
0
        }
7307
0
    }
7308
7309
0
    pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7310
0
                             pszLayerName, pszNewLayerName);
7311
0
    osSQL += pszSQL;
7312
0
    sqlite3_free(pszSQL);
7313
7314
    // Recreate all zoom/tile triggers
7315
0
    if (oTriggerResult)
7316
0
    {
7317
0
        osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7318
0
    }
7319
7320
0
    OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7321
7322
    // Check foreign key integrity
7323
0
    if (eErr == OGRERR_NONE)
7324
0
    {
7325
0
        eErr = PragmaCheck("foreign_key_check", "", 0);
7326
0
    }
7327
7328
0
    if (eErr == OGRERR_NONE)
7329
0
    {
7330
0
        eErr = SoftCommitTransaction();
7331
0
    }
7332
0
    else
7333
0
    {
7334
0
        SoftRollbackTransaction();
7335
0
    }
7336
7337
0
    return eErr == OGRERR_NONE;
7338
0
}
7339
7340
/************************************************************************/
7341
/*                       TestCapability()                               */
7342
/************************************************************************/
7343
7344
int GDALGeoPackageDataset::TestCapability(const char *pszCap)
7345
2.21k
{
7346
2.21k
    if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7347
2.21k
        EQUAL(pszCap, "RenameLayer"))
7348
1.03k
    {
7349
1.03k
        return GetUpdate();
7350
1.03k
    }
7351
1.17k
    else if (EQUAL(pszCap, ODsCCurveGeometries))
7352
22
        return TRUE;
7353
1.15k
    else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7354
0
        return TRUE;
7355
1.15k
    else if (EQUAL(pszCap, ODsCZGeometries))
7356
0
        return TRUE;
7357
1.15k
    else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7358
1.15k
             EQUAL(pszCap, GDsCAddRelationship) ||
7359
1.15k
             EQUAL(pszCap, GDsCDeleteRelationship) ||
7360
1.15k
             EQUAL(pszCap, GDsCUpdateRelationship) ||
7361
1.15k
             EQUAL(pszCap, ODsCAddFieldDomain))
7362
0
        return GetUpdate();
7363
7364
1.15k
    return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7365
2.21k
}
7366
7367
/************************************************************************/
7368
/*                       ResetReadingAllLayers()                        */
7369
/************************************************************************/
7370
7371
void GDALGeoPackageDataset::ResetReadingAllLayers()
7372
0
{
7373
0
    for (auto &poLayer : m_apoLayers)
7374
0
    {
7375
0
        poLayer->ResetReading();
7376
0
    }
7377
0
}
7378
7379
/************************************************************************/
7380
/*                             ExecuteSQL()                             */
7381
/************************************************************************/
7382
7383
static const char *const apszFuncsWithSideEffects[] = {
7384
    "CreateSpatialIndex",
7385
    "DisableSpatialIndex",
7386
    "HasSpatialIndex",
7387
    "RegisterGeometryExtension",
7388
};
7389
7390
OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7391
                                            OGRGeometry *poSpatialFilter,
7392
                                            const char *pszDialect)
7393
7394
0
{
7395
0
    m_bHasReadMetadataFromStorage = false;
7396
7397
0
    FlushMetadata();
7398
7399
0
    while (*pszSQLCommand != '\0' &&
7400
0
           isspace(static_cast<unsigned char>(*pszSQLCommand)))
7401
0
        pszSQLCommand++;
7402
7403
0
    CPLString osSQLCommand(pszSQLCommand);
7404
0
    if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7405
0
        osSQLCommand.pop_back();
7406
7407
0
    if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7408
0
    {
7409
        // Some SQL commands will influence the feature count behind our
7410
        // back, so disable it in that case.
7411
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
7412
0
        const bool bInsertOrDelete =
7413
0
            osSQLCommand.ifind("insert into ") != std::string::npos ||
7414
0
            osSQLCommand.ifind("insert or replace into ") !=
7415
0
                std::string::npos ||
7416
0
            osSQLCommand.ifind("delete from ") != std::string::npos;
7417
0
        const bool bRollback =
7418
0
            osSQLCommand.ifind("rollback ") != std::string::npos;
7419
0
#endif
7420
7421
0
        for (auto &poLayer : m_apoLayers)
7422
0
        {
7423
0
            if (poLayer->SyncToDisk() != OGRERR_NONE)
7424
0
                return nullptr;
7425
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
7426
0
            if (bRollback ||
7427
0
                (bInsertOrDelete &&
7428
0
                 osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7429
0
            {
7430
0
                poLayer->DisableFeatureCount();
7431
0
            }
7432
0
#endif
7433
0
        }
7434
0
    }
7435
7436
0
    if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7437
0
        EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7438
0
        EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7439
0
        EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7440
0
    {
7441
0
        OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7442
0
    }
7443
0
    else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7444
0
             EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7445
0
             EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7446
0
             EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7447
0
    {
7448
0
        OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7449
0
    }
7450
7451
    /* -------------------------------------------------------------------- */
7452
    /*      DEBUG "SELECT nolock" command.                                  */
7453
    /* -------------------------------------------------------------------- */
7454
0
    if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7455
0
        EQUAL(osSQLCommand, "SELECT nolock"))
7456
0
    {
7457
0
        return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7458
0
    }
7459
7460
    /* -------------------------------------------------------------------- */
7461
    /*      Special case DELLAYER: command.                                 */
7462
    /* -------------------------------------------------------------------- */
7463
0
    if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7464
0
    {
7465
0
        const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7466
7467
0
        while (*pszLayerName == ' ')
7468
0
            pszLayerName++;
7469
7470
0
        if (!DeleteVectorOrRasterLayer(pszLayerName))
7471
0
        {
7472
0
            CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7473
0
                     pszLayerName);
7474
0
        }
7475
0
        return nullptr;
7476
0
    }
7477
7478
    /* -------------------------------------------------------------------- */
7479
    /*      Special case RECOMPUTE EXTENT ON command.                       */
7480
    /* -------------------------------------------------------------------- */
7481
0
    if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7482
0
    {
7483
0
        const char *pszLayerName =
7484
0
            osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7485
7486
0
        while (*pszLayerName == ' ')
7487
0
            pszLayerName++;
7488
7489
0
        int idx = FindLayerIndex(pszLayerName);
7490
0
        if (idx >= 0)
7491
0
        {
7492
0
            m_apoLayers[idx]->RecomputeExtent();
7493
0
        }
7494
0
        else
7495
0
            CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7496
0
                     pszLayerName);
7497
0
        return nullptr;
7498
0
    }
7499
7500
    /* -------------------------------------------------------------------- */
7501
    /*      Intercept DROP TABLE                                            */
7502
    /* -------------------------------------------------------------------- */
7503
0
    if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7504
0
    {
7505
0
        const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7506
7507
0
        while (*pszLayerName == ' ')
7508
0
            pszLayerName++;
7509
7510
0
        if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7511
0
            return nullptr;
7512
0
    }
7513
7514
    /* -------------------------------------------------------------------- */
7515
    /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
7516
    /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
7517
    /*      and       ALTER TABLE table DROP COLUMN col_name                */
7518
    /*                                                                      */
7519
    /*      We do this because SQLite mechanisms can't deal with updating   */
7520
    /*      literal values in gpkg_ tables that refer to table and column   */
7521
    /*      names.                                                          */
7522
    /* -------------------------------------------------------------------- */
7523
0
    if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7524
0
    {
7525
0
        char **papszTokens = SQLTokenize(osSQLCommand);
7526
        /* ALTER TABLE src_table RENAME TO dst_table */
7527
0
        if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7528
0
            EQUAL(papszTokens[4], "TO"))
7529
0
        {
7530
0
            const char *pszSrcTableName = papszTokens[2];
7531
0
            const char *pszDstTableName = papszTokens[5];
7532
0
            if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7533
0
                                          SQLUnescape(pszDstTableName)))
7534
0
            {
7535
0
                CSLDestroy(papszTokens);
7536
0
                return nullptr;
7537
0
            }
7538
0
        }
7539
        /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7540
0
        else if (CSLCount(papszTokens) == 8 &&
7541
0
                 EQUAL(papszTokens[3], "RENAME") &&
7542
0
                 EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7543
0
        {
7544
0
            const char *pszTableName = papszTokens[2];
7545
0
            const char *pszSrcColumn = papszTokens[5];
7546
0
            const char *pszDstColumn = papszTokens[7];
7547
0
            OGRGeoPackageTableLayer *poLayer =
7548
0
                dynamic_cast<OGRGeoPackageTableLayer *>(
7549
0
                    GetLayerByName(SQLUnescape(pszTableName)));
7550
0
            if (poLayer)
7551
0
            {
7552
0
                int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7553
0
                    SQLUnescape(pszSrcColumn));
7554
0
                if (nSrcFieldIdx >= 0)
7555
0
                {
7556
                    // OFTString or any type will do as we just alter the name
7557
                    // so it will be ignored.
7558
0
                    OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7559
0
                                            OFTString);
7560
0
                    poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7561
0
                                            ALTER_NAME_FLAG);
7562
0
                    CSLDestroy(papszTokens);
7563
0
                    return nullptr;
7564
0
                }
7565
0
            }
7566
0
        }
7567
        /* ALTER TABLE table DROP COLUMN col_name */
7568
0
        else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7569
0
                 EQUAL(papszTokens[4], "COLUMN"))
7570
0
        {
7571
0
            const char *pszTableName = papszTokens[2];
7572
0
            const char *pszColumnName = papszTokens[5];
7573
0
            OGRGeoPackageTableLayer *poLayer =
7574
0
                dynamic_cast<OGRGeoPackageTableLayer *>(
7575
0
                    GetLayerByName(SQLUnescape(pszTableName)));
7576
0
            if (poLayer)
7577
0
            {
7578
0
                int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7579
0
                    SQLUnescape(pszColumnName));
7580
0
                if (nFieldIdx >= 0)
7581
0
                {
7582
0
                    poLayer->DeleteField(nFieldIdx);
7583
0
                    CSLDestroy(papszTokens);
7584
0
                    return nullptr;
7585
0
                }
7586
0
            }
7587
0
        }
7588
0
        CSLDestroy(papszTokens);
7589
0
    }
7590
7591
0
    if (ProcessTransactionSQL(osSQLCommand))
7592
0
    {
7593
0
        return nullptr;
7594
0
    }
7595
7596
0
    if (EQUAL(osSQLCommand, "VACUUM"))
7597
0
    {
7598
0
        ResetReadingAllLayers();
7599
0
    }
7600
0
    else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7601
0
    {
7602
        // Optimize truncation of a table, especially if it has a spatial
7603
        // index.
7604
0
        const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7605
0
        if (aosTokens.size() == 3)
7606
0
        {
7607
0
            const char *pszTableName = aosTokens[2];
7608
0
            OGRGeoPackageTableLayer *poLayer =
7609
0
                dynamic_cast<OGRGeoPackageTableLayer *>(
7610
0
                    GetLayerByName(SQLUnescape(pszTableName)));
7611
0
            if (poLayer)
7612
0
            {
7613
0
                poLayer->Truncate();
7614
0
                return nullptr;
7615
0
            }
7616
0
        }
7617
0
    }
7618
0
    else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7619
0
        return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7620
0
    else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7621
0
             !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7622
0
             !EQUAL(pszDialect, "DEBUG"))
7623
0
        return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7624
0
                                       pszDialect);
7625
7626
    /* -------------------------------------------------------------------- */
7627
    /*      Prepare statement.                                              */
7628
    /* -------------------------------------------------------------------- */
7629
0
    sqlite3_stmt *hSQLStmt = nullptr;
7630
7631
    /* This will speed-up layer creation */
7632
    /* ORDER BY are costly to evaluate and are not necessary to establish */
7633
    /* the layer definition. */
7634
0
    bool bUseStatementForGetNextFeature = true;
7635
0
    bool bEmptyLayer = false;
7636
0
    CPLString osSQLCommandTruncated(osSQLCommand);
7637
7638
0
    if (osSQLCommand.ifind("SELECT ") == 0 &&
7639
0
        CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7640
0
            std::string::npos &&
7641
0
        osSQLCommand.ifind(" UNION ") == std::string::npos &&
7642
0
        osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7643
0
        osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7644
0
    {
7645
0
        size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7646
0
        if (nOrderByPos != std::string::npos)
7647
0
        {
7648
0
            osSQLCommandTruncated.resize(nOrderByPos);
7649
0
            bUseStatementForGetNextFeature = false;
7650
0
        }
7651
0
    }
7652
7653
0
    int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7654
0
                        static_cast<int>(osSQLCommandTruncated.size()),
7655
0
                        &hSQLStmt, nullptr);
7656
7657
0
    if (rc != SQLITE_OK)
7658
0
    {
7659
0
        CPLError(CE_Failure, CPLE_AppDefined,
7660
0
                 "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
7661
0
                 osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7662
7663
0
        if (hSQLStmt != nullptr)
7664
0
        {
7665
0
            sqlite3_finalize(hSQLStmt);
7666
0
        }
7667
7668
0
        return nullptr;
7669
0
    }
7670
7671
    /* -------------------------------------------------------------------- */
7672
    /*      Do we get a resultset?                                          */
7673
    /* -------------------------------------------------------------------- */
7674
0
    rc = sqlite3_step(hSQLStmt);
7675
7676
0
    for (auto &poLayer : m_apoLayers)
7677
0
    {
7678
0
        poLayer->RunDeferredDropRTreeTableIfNecessary();
7679
0
    }
7680
7681
0
    if (rc != SQLITE_ROW)
7682
0
    {
7683
0
        if (rc != SQLITE_DONE)
7684
0
        {
7685
0
            CPLError(CE_Failure, CPLE_AppDefined,
7686
0
                     "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
7687
0
                     osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7688
7689
0
            sqlite3_finalize(hSQLStmt);
7690
0
            return nullptr;
7691
0
        }
7692
7693
0
        if (EQUAL(osSQLCommand, "VACUUM"))
7694
0
        {
7695
0
            sqlite3_finalize(hSQLStmt);
7696
            /* VACUUM rewrites the DB, so we need to reset the application id */
7697
0
            SetApplicationAndUserVersionId();
7698
0
            return nullptr;
7699
0
        }
7700
7701
0
        if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7702
0
        {
7703
0
            sqlite3_finalize(hSQLStmt);
7704
0
            return nullptr;
7705
0
        }
7706
7707
0
        bUseStatementForGetNextFeature = false;
7708
0
        bEmptyLayer = true;
7709
0
    }
7710
7711
    /* -------------------------------------------------------------------- */
7712
    /*      Special case for some functions which must be run               */
7713
    /*      only once                                                       */
7714
    /* -------------------------------------------------------------------- */
7715
0
    if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7716
0
    {
7717
0
        for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7718
0
                                         sizeof(apszFuncsWithSideEffects[0]);
7719
0
             i++)
7720
0
        {
7721
0
            if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7722
0
                       strlen(apszFuncsWithSideEffects[i])))
7723
0
            {
7724
0
                if (sqlite3_column_count(hSQLStmt) == 1 &&
7725
0
                    sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7726
0
                {
7727
0
                    int ret = sqlite3_column_int(hSQLStmt, 0);
7728
7729
0
                    sqlite3_finalize(hSQLStmt);
7730
7731
0
                    return new OGRSQLiteSingleFeatureLayer(
7732
0
                        apszFuncsWithSideEffects[i], ret);
7733
0
                }
7734
0
            }
7735
0
        }
7736
0
    }
7737
0
    else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7738
0
    {
7739
0
        if (sqlite3_column_count(hSQLStmt) == 1 &&
7740
0
            sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7741
0
        {
7742
0
            int ret = sqlite3_column_int(hSQLStmt, 0);
7743
7744
0
            sqlite3_finalize(hSQLStmt);
7745
7746
0
            return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7747
0
                                                   ret);
7748
0
        }
7749
0
        else if (sqlite3_column_count(hSQLStmt) == 1 &&
7750
0
                 sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7751
0
        {
7752
0
            const char *pszRet = reinterpret_cast<const char *>(
7753
0
                sqlite3_column_text(hSQLStmt, 0));
7754
7755
0
            OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7756
0
                osSQLCommand.c_str() + 7, pszRet);
7757
7758
0
            sqlite3_finalize(hSQLStmt);
7759
7760
0
            return poRet;
7761
0
        }
7762
0
    }
7763
7764
    /* -------------------------------------------------------------------- */
7765
    /*      Create layer.                                                   */
7766
    /* -------------------------------------------------------------------- */
7767
7768
0
    auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7769
0
        this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7770
0
        bEmptyLayer);
7771
7772
0
    if (poSpatialFilter != nullptr &&
7773
0
        poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7774
0
        poLayer->SetSpatialFilter(0, poSpatialFilter);
7775
7776
0
    return poLayer.release();
7777
0
}
7778
7779
/************************************************************************/
7780
/*                          ReleaseResultSet()                          */
7781
/************************************************************************/
7782
7783
void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7784
7785
0
{
7786
0
    delete poLayer;
7787
0
}
7788
7789
/************************************************************************/
7790
/*                         HasExtensionsTable()                         */
7791
/************************************************************************/
7792
7793
bool GDALGeoPackageDataset::HasExtensionsTable()
7794
2.28k
{
7795
2.28k
    return SQLGetInteger(
7796
2.28k
               hDB,
7797
2.28k
               "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7798
2.28k
               "AND type IN ('table', 'view')",
7799
2.28k
               nullptr) == 1;
7800
2.28k
}
7801
7802
/************************************************************************/
7803
/*                    CheckUnknownExtensions()                          */
7804
/************************************************************************/
7805
7806
void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7807
445
{
7808
445
    if (!HasExtensionsTable())
7809
244
        return;
7810
7811
201
    char *pszSQL = nullptr;
7812
201
    if (!bCheckRasterTable)
7813
199
        pszSQL = sqlite3_mprintf(
7814
199
            "SELECT extension_name, definition, scope FROM gpkg_extensions "
7815
199
            "WHERE (table_name IS NULL "
7816
199
            "AND extension_name IS NOT NULL "
7817
199
            "AND definition IS NOT NULL "
7818
199
            "AND scope IS NOT NULL "
7819
199
            "AND extension_name NOT IN ("
7820
199
            "'gdal_aspatial', "
7821
199
            "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
7822
199
            "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
7823
                                       // 17-066r1 finalization
7824
199
            "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
7825
199
            "'gpkg_metadata', "
7826
199
            "'gpkg_schema', "
7827
199
            "'gpkg_crs_wkt', "
7828
199
            "'gpkg_crs_wkt_1_1', "
7829
199
            "'related_tables', 'gpkg_related_tables')) "
7830
#ifdef WORKAROUND_SQLITE3_BUGS
7831
            "OR 0 "
7832
#endif
7833
199
            "LIMIT 1000");
7834
2
    else
7835
2
        pszSQL = sqlite3_mprintf(
7836
2
            "SELECT extension_name, definition, scope FROM gpkg_extensions "
7837
2
            "WHERE (lower(table_name) = lower('%q') "
7838
2
            "AND extension_name IS NOT NULL "
7839
2
            "AND definition IS NOT NULL "
7840
2
            "AND scope IS NOT NULL "
7841
2
            "AND extension_name NOT IN ("
7842
2
            "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
7843
2
            "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
7844
                                       // 17-066r1 finalization
7845
2
            "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
7846
2
            "'gpkg_metadata', "
7847
2
            "'gpkg_schema', "
7848
2
            "'gpkg_crs_wkt', "
7849
2
            "'gpkg_crs_wkt_1_1', "
7850
2
            "'related_tables', 'gpkg_related_tables')) "
7851
#ifdef WORKAROUND_SQLITE3_BUGS
7852
            "OR 0 "
7853
#endif
7854
2
            "LIMIT 1000",
7855
2
            m_osRasterTable.c_str());
7856
7857
201
    auto oResultTable = SQLQuery(GetDB(), pszSQL);
7858
201
    sqlite3_free(pszSQL);
7859
201
    if (oResultTable && oResultTable->RowCount() > 0)
7860
0
    {
7861
0
        for (int i = 0; i < oResultTable->RowCount(); i++)
7862
0
        {
7863
0
            const char *pszExtName = oResultTable->GetValue(0, i);
7864
0
            const char *pszDefinition = oResultTable->GetValue(1, i);
7865
0
            const char *pszScope = oResultTable->GetValue(2, i);
7866
0
            if (pszExtName == nullptr || pszDefinition == nullptr ||
7867
0
                pszScope == nullptr)
7868
0
            {
7869
0
                continue;
7870
0
            }
7871
7872
0
            if (EQUAL(pszExtName, "gpkg_webp"))
7873
0
            {
7874
0
                if (GDALGetDriverByName("WEBP") == nullptr)
7875
0
                {
7876
0
                    CPLError(
7877
0
                        CE_Warning, CPLE_AppDefined,
7878
0
                        "Table %s contains WEBP tiles, but GDAL configured "
7879
0
                        "without WEBP support. Data will be missing",
7880
0
                        m_osRasterTable.c_str());
7881
0
                }
7882
0
                m_eTF = GPKG_TF_WEBP;
7883
0
                continue;
7884
0
            }
7885
0
            if (EQUAL(pszExtName, "gpkg_zoom_other"))
7886
0
            {
7887
0
                m_bZoomOther = true;
7888
0
                continue;
7889
0
            }
7890
7891
0
            if (GetUpdate() && EQUAL(pszScope, "write-only"))
7892
0
            {
7893
0
                CPLError(
7894
0
                    CE_Warning, CPLE_AppDefined,
7895
0
                    "Database relies on the '%s' (%s) extension that should "
7896
0
                    "be implemented for safe write-support, but is not "
7897
0
                    "currently. "
7898
0
                    "Update of that database are strongly discouraged to avoid "
7899
0
                    "corruption.",
7900
0
                    pszExtName, pszDefinition);
7901
0
            }
7902
0
            else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7903
0
            {
7904
0
                CPLError(
7905
0
                    CE_Warning, CPLE_AppDefined,
7906
0
                    "Database relies on the '%s' (%s) extension that should "
7907
0
                    "be implemented in order to read/write it safely, but is "
7908
0
                    "not currently. "
7909
0
                    "Some data may be missing while reading that database, and "
7910
0
                    "updates are strongly discouraged.",
7911
0
                    pszExtName, pszDefinition);
7912
0
            }
7913
0
            else if (EQUAL(pszScope, "read-write") &&
7914
                     // None of the NGA extensions at
7915
                     // http://ngageoint.github.io/GeoPackage/docs/extensions/
7916
                     // affect read-only scenarios
7917
0
                     !STARTS_WITH(pszExtName, "nga_"))
7918
0
            {
7919
0
                CPLError(
7920
0
                    CE_Warning, CPLE_AppDefined,
7921
0
                    "Database relies on the '%s' (%s) extension that should "
7922
0
                    "be implemented in order to read it safely, but is not "
7923
0
                    "currently. "
7924
0
                    "Some data may be missing while reading that database.",
7925
0
                    pszExtName, pszDefinition);
7926
0
            }
7927
0
        }
7928
0
    }
7929
201
}
7930
7931
/************************************************************************/
7932
/*                         HasGDALAspatialExtension()                       */
7933
/************************************************************************/
7934
7935
bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7936
414
{
7937
414
    if (!HasExtensionsTable())
7938
223
        return false;
7939
7940
191
    auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7941
191
                                      "WHERE (extension_name = 'gdal_aspatial' "
7942
191
                                      "AND table_name IS NULL "
7943
191
                                      "AND column_name IS NULL)"
7944
#ifdef WORKAROUND_SQLITE3_BUGS
7945
                                      " OR 0"
7946
#endif
7947
191
    );
7948
191
    bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7949
191
    return bHasExtension;
7950
414
}
7951
7952
std::string
7953
GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7954
0
{
7955
0
    char *pszSQL;
7956
0
    std::string osSQL;
7957
    /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7958
     * Definition SQL  */
7959
0
    pszSQL = sqlite3_mprintf(
7960
0
        "CREATE TRIGGER \"%w_zoom_insert\" "
7961
0
        "BEFORE INSERT ON \"%w\" "
7962
0
        "FOR EACH ROW BEGIN "
7963
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7964
0
        "constraint: zoom_level not specified for table in "
7965
0
        "gpkg_tile_matrix') "
7966
0
        "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7967
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7968
0
        "END; "
7969
0
        "CREATE TRIGGER \"%w_zoom_update\" "
7970
0
        "BEFORE UPDATE OF zoom_level ON \"%w\" "
7971
0
        "FOR EACH ROW BEGIN "
7972
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7973
0
        "constraint: zoom_level not specified for table in "
7974
0
        "gpkg_tile_matrix') "
7975
0
        "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7976
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7977
0
        "END; "
7978
0
        "CREATE TRIGGER \"%w_tile_column_insert\" "
7979
0
        "BEFORE INSERT ON \"%w\" "
7980
0
        "FOR EACH ROW BEGIN "
7981
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7982
0
        "constraint: tile_column cannot be < 0') "
7983
0
        "WHERE (NEW.tile_column < 0) ; "
7984
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7985
0
        "constraint: tile_column must by < matrix_width specified for "
7986
0
        "table and zoom level in gpkg_tile_matrix') "
7987
0
        "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
7988
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
7989
0
        "zoom_level = NEW.zoom_level)); "
7990
0
        "END; "
7991
0
        "CREATE TRIGGER \"%w_tile_column_update\" "
7992
0
        "BEFORE UPDATE OF tile_column ON \"%w\" "
7993
0
        "FOR EACH ROW BEGIN "
7994
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7995
0
        "constraint: tile_column cannot be < 0') "
7996
0
        "WHERE (NEW.tile_column < 0) ; "
7997
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7998
0
        "constraint: tile_column must by < matrix_width specified for "
7999
0
        "table and zoom level in gpkg_tile_matrix') "
8000
0
        "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8001
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8002
0
        "zoom_level = NEW.zoom_level)); "
8003
0
        "END; "
8004
0
        "CREATE TRIGGER \"%w_tile_row_insert\" "
8005
0
        "BEFORE INSERT ON \"%w\" "
8006
0
        "FOR EACH ROW BEGIN "
8007
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8008
0
        "constraint: tile_row cannot be < 0') "
8009
0
        "WHERE (NEW.tile_row < 0) ; "
8010
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8011
0
        "constraint: tile_row must by < matrix_height specified for "
8012
0
        "table and zoom level in gpkg_tile_matrix') "
8013
0
        "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8014
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8015
0
        "zoom_level = NEW.zoom_level)); "
8016
0
        "END; "
8017
0
        "CREATE TRIGGER \"%w_tile_row_update\" "
8018
0
        "BEFORE UPDATE OF tile_row ON \"%w\" "
8019
0
        "FOR EACH ROW BEGIN "
8020
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8021
0
        "constraint: tile_row cannot be < 0') "
8022
0
        "WHERE (NEW.tile_row < 0) ; "
8023
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8024
0
        "constraint: tile_row must by < matrix_height specified for "
8025
0
        "table and zoom level in gpkg_tile_matrix') "
8026
0
        "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8027
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8028
0
        "zoom_level = NEW.zoom_level)); "
8029
0
        "END; ",
8030
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8031
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8032
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8033
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8034
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8035
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8036
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8037
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8038
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8039
0
        osTableName.c_str());
8040
0
    osSQL = pszSQL;
8041
0
    sqlite3_free(pszSQL);
8042
0
    return osSQL;
8043
0
}
8044
8045
/************************************************************************/
8046
/*                  CreateExtensionsTableIfNecessary()                  */
8047
/************************************************************************/
8048
8049
OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8050
503
{
8051
    /* Check if the table gpkg_extensions exists */
8052
503
    if (HasExtensionsTable())
8053
471
        return OGRERR_NONE;
8054
8055
    /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8056
    /* in a corresponding row in the gpkg_extensions table. The absence of a */
8057
    /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8058
    /* SHALL both indicate the absence of extensions to a GeoPackage. */
8059
32
    const char *pszCreateGpkgExtensions =
8060
32
        "CREATE TABLE gpkg_extensions ("
8061
32
        "table_name TEXT,"
8062
32
        "column_name TEXT,"
8063
32
        "extension_name TEXT NOT NULL,"
8064
32
        "definition TEXT NOT NULL,"
8065
32
        "scope TEXT NOT NULL,"
8066
32
        "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8067
32
        ")";
8068
8069
32
    return SQLCommand(hDB, pszCreateGpkgExtensions);
8070
503
}
8071
8072
/************************************************************************/
8073
/*                    OGR_GPKG_Intersects_Spatial_Filter()              */
8074
/************************************************************************/
8075
8076
void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8077
                                        sqlite3_value **argv)
8078
0
{
8079
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8080
0
    {
8081
0
        sqlite3_result_int(pContext, 0);
8082
0
        return;
8083
0
    }
8084
8085
0
    auto poLayer =
8086
0
        static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8087
8088
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8089
0
    const GByte *pabyBLOB =
8090
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8091
8092
0
    GPkgHeader sHeader;
8093
0
    if (poLayer->m_bFilterIsEnvelope &&
8094
0
        OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8095
0
    {
8096
0
        if (sHeader.bExtentHasXY)
8097
0
        {
8098
0
            OGREnvelope sEnvelope;
8099
0
            sEnvelope.MinX = sHeader.MinX;
8100
0
            sEnvelope.MinY = sHeader.MinY;
8101
0
            sEnvelope.MaxX = sHeader.MaxX;
8102
0
            sEnvelope.MaxY = sHeader.MaxY;
8103
0
            if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8104
0
            {
8105
0
                sqlite3_result_int(pContext, 1);
8106
0
                return;
8107
0
            }
8108
0
        }
8109
8110
        // Check if at least one point falls into the layer filter envelope
8111
        // nHeaderLen is > 0 for GeoPackage geometries
8112
0
        if (sHeader.nHeaderLen > 0 &&
8113
0
            OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8114
0
                                        nBLOBLen - sHeader.nHeaderLen,
8115
0
                                        poLayer->m_sFilterEnvelope))
8116
0
        {
8117
0
            sqlite3_result_int(pContext, 1);
8118
0
            return;
8119
0
        }
8120
0
    }
8121
8122
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8123
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8124
0
    if (poGeom == nullptr)
8125
0
    {
8126
        // Try also spatialite geometry blobs
8127
0
        OGRGeometry *poGeomSpatialite = nullptr;
8128
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8129
0
                                              &poGeomSpatialite) != OGRERR_NONE)
8130
0
        {
8131
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8132
0
            sqlite3_result_int(pContext, 0);
8133
0
            return;
8134
0
        }
8135
0
        poGeom.reset(poGeomSpatialite);
8136
0
    }
8137
8138
0
    sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8139
0
}
8140
8141
/************************************************************************/
8142
/*                      OGRGeoPackageSTMinX()                           */
8143
/************************************************************************/
8144
8145
static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8146
                                sqlite3_value **argv)
8147
338
{
8148
338
    GPkgHeader sHeader;
8149
338
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8150
251
    {
8151
251
        sqlite3_result_null(pContext);
8152
251
        return;
8153
251
    }
8154
87
    sqlite3_result_double(pContext, sHeader.MinX);
8155
87
}
8156
8157
/************************************************************************/
8158
/*                      OGRGeoPackageSTMinY()                           */
8159
/************************************************************************/
8160
8161
static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8162
                                sqlite3_value **argv)
8163
338
{
8164
338
    GPkgHeader sHeader;
8165
338
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8166
251
    {
8167
251
        sqlite3_result_null(pContext);
8168
251
        return;
8169
251
    }
8170
87
    sqlite3_result_double(pContext, sHeader.MinY);
8171
87
}
8172
8173
/************************************************************************/
8174
/*                      OGRGeoPackageSTMaxX()                           */
8175
/************************************************************************/
8176
8177
static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8178
                                sqlite3_value **argv)
8179
338
{
8180
338
    GPkgHeader sHeader;
8181
338
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8182
251
    {
8183
251
        sqlite3_result_null(pContext);
8184
251
        return;
8185
251
    }
8186
87
    sqlite3_result_double(pContext, sHeader.MaxX);
8187
87
}
8188
8189
/************************************************************************/
8190
/*                      OGRGeoPackageSTMaxY()                           */
8191
/************************************************************************/
8192
8193
static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8194
                                sqlite3_value **argv)
8195
338
{
8196
338
    GPkgHeader sHeader;
8197
338
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8198
251
    {
8199
251
        sqlite3_result_null(pContext);
8200
251
        return;
8201
251
    }
8202
87
    sqlite3_result_double(pContext, sHeader.MaxY);
8203
87
}
8204
8205
/************************************************************************/
8206
/*                     OGRGeoPackageSTIsEmpty()                         */
8207
/************************************************************************/
8208
8209
static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8210
                                   sqlite3_value **argv)
8211
265
{
8212
265
    GPkgHeader sHeader;
8213
265
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8214
0
    {
8215
0
        sqlite3_result_null(pContext);
8216
0
        return;
8217
0
    }
8218
265
    sqlite3_result_int(pContext, sHeader.bEmpty);
8219
265
}
8220
8221
/************************************************************************/
8222
/*                    OGRGeoPackageSTGeometryType()                     */
8223
/************************************************************************/
8224
8225
static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8226
                                        sqlite3_value **argv)
8227
0
{
8228
0
    GPkgHeader sHeader;
8229
8230
0
    int nBLOBLen = sqlite3_value_bytes(argv[0]);
8231
0
    const GByte *pabyBLOB =
8232
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8233
0
    OGRwkbGeometryType eGeometryType;
8234
8235
0
    if (nBLOBLen < 8 ||
8236
0
        GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8237
0
    {
8238
0
        if (OGRSQLiteGetSpatialiteGeometryHeader(
8239
0
                pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8240
0
                nullptr, nullptr, nullptr) == OGRERR_NONE)
8241
0
        {
8242
0
            sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8243
0
                                SQLITE_TRANSIENT);
8244
0
            return;
8245
0
        }
8246
0
        else
8247
0
        {
8248
0
            sqlite3_result_null(pContext);
8249
0
            return;
8250
0
        }
8251
0
    }
8252
8253
0
    if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8254
0
    {
8255
0
        sqlite3_result_null(pContext);
8256
0
        return;
8257
0
    }
8258
8259
0
    OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8260
0
                                        wkbVariantIso, &eGeometryType);
8261
0
    if (err != OGRERR_NONE)
8262
0
        sqlite3_result_null(pContext);
8263
0
    else
8264
0
        sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8265
0
                            SQLITE_TRANSIENT);
8266
0
}
8267
8268
/************************************************************************/
8269
/*                 OGRGeoPackageSTEnvelopesIntersects()                 */
8270
/************************************************************************/
8271
8272
static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8273
                                               int argc, sqlite3_value **argv)
8274
0
{
8275
0
    GPkgHeader sHeader;
8276
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8277
0
    {
8278
0
        sqlite3_result_int(pContext, FALSE);
8279
0
        return;
8280
0
    }
8281
0
    const double dfMinX = sqlite3_value_double(argv[1]);
8282
0
    if (sHeader.MaxX < dfMinX)
8283
0
    {
8284
0
        sqlite3_result_int(pContext, FALSE);
8285
0
        return;
8286
0
    }
8287
0
    const double dfMinY = sqlite3_value_double(argv[2]);
8288
0
    if (sHeader.MaxY < dfMinY)
8289
0
    {
8290
0
        sqlite3_result_int(pContext, FALSE);
8291
0
        return;
8292
0
    }
8293
0
    const double dfMaxX = sqlite3_value_double(argv[3]);
8294
0
    if (sHeader.MinX > dfMaxX)
8295
0
    {
8296
0
        sqlite3_result_int(pContext, FALSE);
8297
0
        return;
8298
0
    }
8299
0
    const double dfMaxY = sqlite3_value_double(argv[4]);
8300
0
    sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8301
0
}
8302
8303
/************************************************************************/
8304
/*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
8305
/************************************************************************/
8306
8307
static void
8308
OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8309
                                            sqlite3_value **argv)
8310
0
{
8311
0
    GPkgHeader sHeader;
8312
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8313
0
    {
8314
0
        sqlite3_result_int(pContext, FALSE);
8315
0
        return;
8316
0
    }
8317
0
    GPkgHeader sHeader2;
8318
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8319
0
                                1))
8320
0
    {
8321
0
        sqlite3_result_int(pContext, FALSE);
8322
0
        return;
8323
0
    }
8324
0
    if (sHeader.MaxX < sHeader2.MinX)
8325
0
    {
8326
0
        sqlite3_result_int(pContext, FALSE);
8327
0
        return;
8328
0
    }
8329
0
    if (sHeader.MaxY < sHeader2.MinY)
8330
0
    {
8331
0
        sqlite3_result_int(pContext, FALSE);
8332
0
        return;
8333
0
    }
8334
0
    if (sHeader.MinX > sHeader2.MaxX)
8335
0
    {
8336
0
        sqlite3_result_int(pContext, FALSE);
8337
0
        return;
8338
0
    }
8339
0
    sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8340
0
}
8341
8342
/************************************************************************/
8343
/*                    OGRGeoPackageGPKGIsAssignable()                   */
8344
/************************************************************************/
8345
8346
static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8347
                                          int /*argc*/, sqlite3_value **argv)
8348
0
{
8349
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8350
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8351
0
    {
8352
0
        sqlite3_result_int(pContext, 0);
8353
0
        return;
8354
0
    }
8355
8356
0
    const char *pszExpected =
8357
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8358
0
    const char *pszActual =
8359
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8360
0
    int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8361
0
                                            OGRFromOGCGeomType(pszExpected));
8362
0
    sqlite3_result_int(pContext, bIsAssignable);
8363
0
}
8364
8365
/************************************************************************/
8366
/*                     OGRGeoPackageSTSRID()                            */
8367
/************************************************************************/
8368
8369
static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8370
                                sqlite3_value **argv)
8371
0
{
8372
0
    GPkgHeader sHeader;
8373
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8374
0
    {
8375
0
        sqlite3_result_null(pContext);
8376
0
        return;
8377
0
    }
8378
0
    sqlite3_result_int(pContext, sHeader.iSrsId);
8379
0
}
8380
8381
/************************************************************************/
8382
/*                     OGRGeoPackageSetSRID()                           */
8383
/************************************************************************/
8384
8385
static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8386
                                 sqlite3_value **argv)
8387
0
{
8388
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8389
0
    {
8390
0
        sqlite3_result_null(pContext);
8391
0
        return;
8392
0
    }
8393
0
    const int nDestSRID = sqlite3_value_int(argv[1]);
8394
0
    GPkgHeader sHeader;
8395
0
    int nBLOBLen = sqlite3_value_bytes(argv[0]);
8396
0
    const GByte *pabyBLOB =
8397
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8398
8399
0
    if (nBLOBLen < 8 ||
8400
0
        GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8401
0
    {
8402
        // Try also spatialite geometry blobs
8403
0
        OGRGeometry *poGeom = nullptr;
8404
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8405
0
            OGRERR_NONE)
8406
0
        {
8407
0
            sqlite3_result_null(pContext);
8408
0
            return;
8409
0
        }
8410
0
        size_t nBLOBDestLen = 0;
8411
0
        GByte *pabyDestBLOB =
8412
0
            GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8413
0
        if (!pabyDestBLOB)
8414
0
        {
8415
0
            sqlite3_result_null(pContext);
8416
0
            return;
8417
0
        }
8418
0
        sqlite3_result_blob(pContext, pabyDestBLOB,
8419
0
                            static_cast<int>(nBLOBDestLen), VSIFree);
8420
0
        return;
8421
0
    }
8422
8423
0
    GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8424
0
    memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8425
0
    int32_t nSRIDToSerialize = nDestSRID;
8426
0
    if (OGR_SWAP(sHeader.eByteOrder))
8427
0
        nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8428
0
    memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8429
0
    sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8430
0
}
8431
8432
/************************************************************************/
8433
/*                   OGRGeoPackageSTMakeValid()                         */
8434
/************************************************************************/
8435
8436
static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8437
                                     sqlite3_value **argv)
8438
0
{
8439
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8440
0
    {
8441
0
        sqlite3_result_null(pContext);
8442
0
        return;
8443
0
    }
8444
0
    int nBLOBLen = sqlite3_value_bytes(argv[0]);
8445
0
    const GByte *pabyBLOB =
8446
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8447
8448
0
    GPkgHeader sHeader;
8449
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8450
0
    {
8451
0
        sqlite3_result_null(pContext);
8452
0
        return;
8453
0
    }
8454
8455
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8456
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8457
0
    if (poGeom == nullptr)
8458
0
    {
8459
        // Try also spatialite geometry blobs
8460
0
        OGRGeometry *poGeomPtr = nullptr;
8461
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8462
0
            OGRERR_NONE)
8463
0
        {
8464
0
            sqlite3_result_null(pContext);
8465
0
            return;
8466
0
        }
8467
0
        poGeom.reset(poGeomPtr);
8468
0
    }
8469
0
    auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8470
0
    if (poValid == nullptr)
8471
0
    {
8472
0
        sqlite3_result_null(pContext);
8473
0
        return;
8474
0
    }
8475
8476
0
    size_t nBLOBDestLen = 0;
8477
0
    GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8478
0
                                              nullptr, &nBLOBDestLen);
8479
0
    if (!pabyDestBLOB)
8480
0
    {
8481
0
        sqlite3_result_null(pContext);
8482
0
        return;
8483
0
    }
8484
0
    sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8485
0
                        VSIFree);
8486
0
}
8487
8488
/************************************************************************/
8489
/*                   OGRGeoPackageSTArea()                              */
8490
/************************************************************************/
8491
8492
static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8493
                                sqlite3_value **argv)
8494
0
{
8495
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8496
0
    {
8497
0
        sqlite3_result_null(pContext);
8498
0
        return;
8499
0
    }
8500
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8501
0
    const GByte *pabyBLOB =
8502
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8503
8504
0
    GPkgHeader sHeader;
8505
0
    std::unique_ptr<OGRGeometry> poGeom;
8506
0
    if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8507
0
    {
8508
0
        if (sHeader.bEmpty)
8509
0
        {
8510
0
            sqlite3_result_double(pContext, 0);
8511
0
            return;
8512
0
        }
8513
0
        const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8514
0
        size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8515
0
        bool bNeedSwap;
8516
0
        uint32_t nType;
8517
0
        if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8518
0
        {
8519
0
            if (nType == wkbPolygon || nType == wkbPolygon25D ||
8520
0
                nType == wkbPolygon + 1000 ||  // wkbPolygonZ
8521
0
                nType == wkbPolygonM || nType == wkbPolygonZM)
8522
0
            {
8523
0
                double dfArea;
8524
0
                if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8525
0
                {
8526
0
                    sqlite3_result_double(pContext, dfArea);
8527
0
                    return;
8528
0
                }
8529
0
            }
8530
0
            else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8531
0
                     nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
8532
0
                     nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8533
0
            {
8534
0
                double dfArea;
8535
0
                if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8536
0
                {
8537
0
                    sqlite3_result_double(pContext, dfArea);
8538
0
                    return;
8539
0
                }
8540
0
            }
8541
0
        }
8542
8543
        // For curve geometries, fallback to OGRGeometry methods
8544
0
        poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8545
0
    }
8546
0
    else
8547
0
    {
8548
        // Try also spatialite geometry blobs
8549
0
        OGRGeometry *poGeomPtr = nullptr;
8550
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8551
0
            OGRERR_NONE)
8552
0
        {
8553
0
            sqlite3_result_null(pContext);
8554
0
            return;
8555
0
        }
8556
0
        poGeom.reset(poGeomPtr);
8557
0
    }
8558
0
    auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8559
0
    if (poSurface == nullptr)
8560
0
    {
8561
0
        auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8562
0
        if (poMultiSurface == nullptr)
8563
0
        {
8564
0
            sqlite3_result_double(pContext, 0);
8565
0
        }
8566
0
        else
8567
0
        {
8568
0
            sqlite3_result_double(pContext, poMultiSurface->get_Area());
8569
0
        }
8570
0
    }
8571
0
    else
8572
0
    {
8573
0
        sqlite3_result_double(pContext, poSurface->get_Area());
8574
0
    }
8575
0
}
8576
8577
/************************************************************************/
8578
/*                     OGRGeoPackageGeodesicArea()                      */
8579
/************************************************************************/
8580
8581
static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8582
                                      sqlite3_value **argv)
8583
0
{
8584
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8585
0
    {
8586
0
        sqlite3_result_null(pContext);
8587
0
        return;
8588
0
    }
8589
0
    if (sqlite3_value_int(argv[1]) != 1)
8590
0
    {
8591
0
        CPLError(CE_Warning, CPLE_NotSupported,
8592
0
                 "ST_Area(geom, use_ellipsoid) is only supported for "
8593
0
                 "use_ellipsoid = 1");
8594
0
    }
8595
8596
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8597
0
    const GByte *pabyBLOB =
8598
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8599
0
    GPkgHeader sHeader;
8600
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8601
0
    {
8602
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8603
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8604
0
        return;
8605
0
    }
8606
8607
0
    GDALGeoPackageDataset *poDS =
8608
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8609
8610
0
    std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8611
0
        poDS->GetSpatialRef(sHeader.iSrsId, true));
8612
0
    if (poSrcSRS == nullptr)
8613
0
    {
8614
0
        CPLError(CE_Failure, CPLE_AppDefined,
8615
0
                 "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8616
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8617
0
        return;
8618
0
    }
8619
8620
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8621
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8622
0
    if (poGeom == nullptr)
8623
0
    {
8624
        // Try also spatialite geometry blobs
8625
0
        OGRGeometry *poGeomSpatialite = nullptr;
8626
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8627
0
                                              &poGeomSpatialite) != OGRERR_NONE)
8628
0
        {
8629
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8630
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8631
0
            return;
8632
0
        }
8633
0
        poGeom.reset(poGeomSpatialite);
8634
0
    }
8635
8636
0
    poGeom->assignSpatialReference(poSrcSRS.get());
8637
0
    sqlite3_result_double(
8638
0
        pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8639
0
}
8640
8641
/************************************************************************/
8642
/*                   OGRGeoPackageLengthOrGeodesicLength()              */
8643
/************************************************************************/
8644
8645
static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8646
                                                int argc, sqlite3_value **argv)
8647
0
{
8648
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8649
0
    {
8650
0
        sqlite3_result_null(pContext);
8651
0
        return;
8652
0
    }
8653
0
    if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8654
0
    {
8655
0
        CPLError(CE_Warning, CPLE_NotSupported,
8656
0
                 "ST_Length(geom, use_ellipsoid) is only supported for "
8657
0
                 "use_ellipsoid = 1");
8658
0
    }
8659
8660
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8661
0
    const GByte *pabyBLOB =
8662
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8663
0
    GPkgHeader sHeader;
8664
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8665
0
    {
8666
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8667
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8668
0
        return;
8669
0
    }
8670
8671
0
    GDALGeoPackageDataset *poDS =
8672
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8673
8674
0
    std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8675
0
    if (argc == 2)
8676
0
    {
8677
0
        poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8678
0
        if (!poSrcSRS)
8679
0
        {
8680
0
            CPLError(CE_Failure, CPLE_AppDefined,
8681
0
                     "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8682
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8683
0
            return;
8684
0
        }
8685
0
    }
8686
8687
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8688
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8689
0
    if (poGeom == nullptr)
8690
0
    {
8691
        // Try also spatialite geometry blobs
8692
0
        OGRGeometry *poGeomSpatialite = nullptr;
8693
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8694
0
                                              &poGeomSpatialite) != OGRERR_NONE)
8695
0
        {
8696
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8697
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8698
0
            return;
8699
0
        }
8700
0
        poGeom.reset(poGeomSpatialite);
8701
0
    }
8702
8703
0
    if (argc == 2)
8704
0
        poGeom->assignSpatialReference(poSrcSRS.get());
8705
8706
0
    sqlite3_result_double(
8707
0
        pContext,
8708
0
        argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8709
0
                  : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8710
0
}
8711
8712
/************************************************************************/
8713
/*                      OGRGeoPackageTransform()                        */
8714
/************************************************************************/
8715
8716
void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8717
                            sqlite3_value **argv);
8718
8719
void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8720
                            sqlite3_value **argv)
8721
0
{
8722
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8723
0
        sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8724
0
    {
8725
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8726
0
        return;
8727
0
    }
8728
8729
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8730
0
    const GByte *pabyBLOB =
8731
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8732
0
    GPkgHeader sHeader;
8733
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8734
0
    {
8735
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8736
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8737
0
        return;
8738
0
    }
8739
8740
0
    const int nDestSRID = sqlite3_value_int(argv[1]);
8741
0
    if (sHeader.iSrsId == nDestSRID)
8742
0
    {
8743
        // Return blob unmodified
8744
0
        sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8745
0
        return;
8746
0
    }
8747
8748
0
    GDALGeoPackageDataset *poDS =
8749
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8750
8751
    // Try to get the cached coordinate transformation
8752
0
    OGRCoordinateTransformation *poCT;
8753
0
    if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8754
0
        poDS->m_nLastCachedCTDstSRId == nDestSRID)
8755
0
    {
8756
0
        poCT = poDS->m_poLastCachedCT.get();
8757
0
    }
8758
0
    else
8759
0
    {
8760
0
        std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8761
0
            poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8762
0
        if (poSrcSRS == nullptr)
8763
0
        {
8764
0
            CPLError(CE_Failure, CPLE_AppDefined,
8765
0
                     "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8766
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8767
0
            return;
8768
0
        }
8769
8770
0
        std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8771
0
            poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8772
0
        if (poDstSRS == nullptr)
8773
0
        {
8774
0
            CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8775
0
                     nDestSRID);
8776
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8777
0
            return;
8778
0
        }
8779
0
        poCT =
8780
0
            OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8781
0
        if (poCT == nullptr)
8782
0
        {
8783
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8784
0
            return;
8785
0
        }
8786
8787
        // Cache coordinate transformation for potential later reuse
8788
0
        poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8789
0
        poDS->m_nLastCachedCTDstSRId = nDestSRID;
8790
0
        poDS->m_poLastCachedCT.reset(poCT);
8791
0
        poCT = poDS->m_poLastCachedCT.get();
8792
0
    }
8793
8794
0
    if (sHeader.nHeaderLen >= 8)
8795
0
    {
8796
0
        std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8797
0
        abyNewBLOB.resize(nBLOBLen);
8798
0
        memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8799
8800
0
        OGREnvelope3D oEnv3d;
8801
0
        if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8802
0
                             nBLOBLen - sHeader.nHeaderLen, poCT,
8803
0
                             poDS->m_oWKBTransformCache, oEnv3d) ||
8804
0
            !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8805
0
                              oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8806
0
                              oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8807
0
        {
8808
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8809
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8810
0
            return;
8811
0
        }
8812
8813
0
        sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8814
0
                            SQLITE_TRANSIENT);
8815
0
        return;
8816
0
    }
8817
8818
    // Try also spatialite geometry blobs
8819
0
    OGRGeometry *poGeomSpatialite = nullptr;
8820
0
    if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8821
0
                                          &poGeomSpatialite) != OGRERR_NONE)
8822
0
    {
8823
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8824
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8825
0
        return;
8826
0
    }
8827
0
    auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8828
8829
0
    if (poGeom->transform(poCT) != OGRERR_NONE)
8830
0
    {
8831
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8832
0
        return;
8833
0
    }
8834
8835
0
    size_t nBLOBDestLen = 0;
8836
0
    GByte *pabyDestBLOB =
8837
0
        GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8838
0
    if (!pabyDestBLOB)
8839
0
    {
8840
0
        sqlite3_result_null(pContext);
8841
0
        return;
8842
0
    }
8843
0
    sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8844
0
                        VSIFree);
8845
0
}
8846
8847
/************************************************************************/
8848
/*                      OGRGeoPackageSridFromAuthCRS()                  */
8849
/************************************************************************/
8850
8851
static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8852
                                         int /*argc*/, sqlite3_value **argv)
8853
0
{
8854
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8855
0
        sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8856
0
    {
8857
0
        sqlite3_result_int(pContext, -1);
8858
0
        return;
8859
0
    }
8860
8861
0
    GDALGeoPackageDataset *poDS =
8862
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8863
8864
0
    char *pszSQL = sqlite3_mprintf(
8865
0
        "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8866
0
        "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8867
0
        sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8868
0
    OGRErr err = OGRERR_NONE;
8869
0
    int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8870
0
    sqlite3_free(pszSQL);
8871
0
    if (err != OGRERR_NONE)
8872
0
        nSRSId = -1;
8873
0
    sqlite3_result_int(pContext, nSRSId);
8874
0
}
8875
8876
/************************************************************************/
8877
/*                    OGRGeoPackageImportFromEPSG()                     */
8878
/************************************************************************/
8879
8880
static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8881
                                        sqlite3_value **argv)
8882
0
{
8883
0
    if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8884
0
    {
8885
0
        sqlite3_result_int(pContext, -1);
8886
0
        return;
8887
0
    }
8888
8889
0
    GDALGeoPackageDataset *poDS =
8890
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8891
0
    OGRSpatialReference oSRS;
8892
0
    if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8893
0
    {
8894
0
        sqlite3_result_int(pContext, -1);
8895
0
        return;
8896
0
    }
8897
8898
0
    sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8899
0
}
8900
8901
/************************************************************************/
8902
/*               OGRGeoPackageRegisterGeometryExtension()               */
8903
/************************************************************************/
8904
8905
static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8906
                                                   int /*argc*/,
8907
                                                   sqlite3_value **argv)
8908
0
{
8909
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8910
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8911
0
        sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8912
0
    {
8913
0
        sqlite3_result_int(pContext, 0);
8914
0
        return;
8915
0
    }
8916
8917
0
    const char *pszTableName =
8918
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8919
0
    const char *pszGeomName =
8920
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8921
0
    const char *pszGeomType =
8922
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8923
8924
0
    GDALGeoPackageDataset *poDS =
8925
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8926
8927
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8928
0
        poDS->GetLayerByName(pszTableName));
8929
0
    if (poLyr == nullptr)
8930
0
    {
8931
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8932
0
        sqlite3_result_int(pContext, 0);
8933
0
        return;
8934
0
    }
8935
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8936
0
    {
8937
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8938
0
        sqlite3_result_int(pContext, 0);
8939
0
        return;
8940
0
    }
8941
0
    const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8942
0
    if (eGeomType == wkbUnknown)
8943
0
    {
8944
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8945
0
        sqlite3_result_int(pContext, 0);
8946
0
        return;
8947
0
    }
8948
8949
0
    sqlite3_result_int(
8950
0
        pContext,
8951
0
        static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8952
0
}
8953
8954
/************************************************************************/
8955
/*                  OGRGeoPackageCreateSpatialIndex()                   */
8956
/************************************************************************/
8957
8958
static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8959
                                            int /*argc*/, sqlite3_value **argv)
8960
0
{
8961
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8962
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8963
0
    {
8964
0
        sqlite3_result_int(pContext, 0);
8965
0
        return;
8966
0
    }
8967
8968
0
    const char *pszTableName =
8969
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8970
0
    const char *pszGeomName =
8971
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8972
0
    GDALGeoPackageDataset *poDS =
8973
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8974
8975
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8976
0
        poDS->GetLayerByName(pszTableName));
8977
0
    if (poLyr == nullptr)
8978
0
    {
8979
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8980
0
        sqlite3_result_int(pContext, 0);
8981
0
        return;
8982
0
    }
8983
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8984
0
    {
8985
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8986
0
        sqlite3_result_int(pContext, 0);
8987
0
        return;
8988
0
    }
8989
8990
0
    sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
8991
0
}
8992
8993
/************************************************************************/
8994
/*                  OGRGeoPackageDisableSpatialIndex()                  */
8995
/************************************************************************/
8996
8997
static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
8998
                                             int /*argc*/, sqlite3_value **argv)
8999
0
{
9000
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9001
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9002
0
    {
9003
0
        sqlite3_result_int(pContext, 0);
9004
0
        return;
9005
0
    }
9006
9007
0
    const char *pszTableName =
9008
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9009
0
    const char *pszGeomName =
9010
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9011
0
    GDALGeoPackageDataset *poDS =
9012
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9013
9014
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9015
0
        poDS->GetLayerByName(pszTableName));
9016
0
    if (poLyr == nullptr)
9017
0
    {
9018
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9019
0
        sqlite3_result_int(pContext, 0);
9020
0
        return;
9021
0
    }
9022
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9023
0
    {
9024
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9025
0
        sqlite3_result_int(pContext, 0);
9026
0
        return;
9027
0
    }
9028
9029
0
    sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9030
0
}
9031
9032
/************************************************************************/
9033
/*                  OGRGeoPackageHasSpatialIndex()                      */
9034
/************************************************************************/
9035
9036
static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9037
                                         int /*argc*/, sqlite3_value **argv)
9038
0
{
9039
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9040
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9041
0
    {
9042
0
        sqlite3_result_int(pContext, 0);
9043
0
        return;
9044
0
    }
9045
9046
0
    const char *pszTableName =
9047
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9048
0
    const char *pszGeomName =
9049
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9050
0
    GDALGeoPackageDataset *poDS =
9051
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9052
9053
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9054
0
        poDS->GetLayerByName(pszTableName));
9055
0
    if (poLyr == nullptr)
9056
0
    {
9057
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9058
0
        sqlite3_result_int(pContext, 0);
9059
0
        return;
9060
0
    }
9061
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9062
0
    {
9063
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9064
0
        sqlite3_result_int(pContext, 0);
9065
0
        return;
9066
0
    }
9067
9068
0
    poLyr->RunDeferredCreationIfNecessary();
9069
0
    poLyr->CreateSpatialIndexIfNecessary();
9070
9071
0
    sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9072
0
}
9073
9074
/************************************************************************/
9075
/*                       GPKG_hstore_get_value()                        */
9076
/************************************************************************/
9077
9078
static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9079
                                  sqlite3_value **argv)
9080
0
{
9081
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9082
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9083
0
    {
9084
0
        sqlite3_result_null(pContext);
9085
0
        return;
9086
0
    }
9087
9088
0
    const char *pszHStore =
9089
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9090
0
    const char *pszSearchedKey =
9091
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9092
0
    char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9093
0
    if (pszValue != nullptr)
9094
0
        sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9095
0
    else
9096
0
        sqlite3_result_null(pContext);
9097
0
}
9098
9099
/************************************************************************/
9100
/*                      GPKG_GDAL_GetMemFileFromBlob()                  */
9101
/************************************************************************/
9102
9103
static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9104
0
{
9105
0
    int nBytes = sqlite3_value_bytes(argv[0]);
9106
0
    const GByte *pabyBLOB =
9107
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9108
0
    CPLString osMemFileName(
9109
0
        VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9110
0
    VSILFILE *fp = VSIFileFromMemBuffer(
9111
0
        osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9112
0
    VSIFCloseL(fp);
9113
0
    return osMemFileName;
9114
0
}
9115
9116
/************************************************************************/
9117
/*                       GPKG_GDAL_GetMimeType()                        */
9118
/************************************************************************/
9119
9120
static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9121
                                  sqlite3_value **argv)
9122
0
{
9123
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9124
0
    {
9125
0
        sqlite3_result_null(pContext);
9126
0
        return;
9127
0
    }
9128
9129
0
    CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9130
0
    GDALDriver *poDriver =
9131
0
        GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9132
0
    if (poDriver != nullptr)
9133
0
    {
9134
0
        const char *pszRes = nullptr;
9135
0
        if (EQUAL(poDriver->GetDescription(), "PNG"))
9136
0
            pszRes = "image/png";
9137
0
        else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9138
0
            pszRes = "image/jpeg";
9139
0
        else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9140
0
            pszRes = "image/x-webp";
9141
0
        else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9142
0
            pszRes = "image/tiff";
9143
0
        else
9144
0
            pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9145
0
        sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9146
0
    }
9147
0
    else
9148
0
        sqlite3_result_null(pContext);
9149
0
    VSIUnlink(osMemFileName);
9150
0
}
9151
9152
/************************************************************************/
9153
/*                       GPKG_GDAL_GetBandCount()                       */
9154
/************************************************************************/
9155
9156
static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9157
                                   sqlite3_value **argv)
9158
0
{
9159
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9160
0
    {
9161
0
        sqlite3_result_null(pContext);
9162
0
        return;
9163
0
    }
9164
9165
0
    CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9166
0
    auto poDS = std::unique_ptr<GDALDataset>(
9167
0
        GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9168
0
                          nullptr, nullptr, nullptr));
9169
0
    if (poDS != nullptr)
9170
0
    {
9171
0
        sqlite3_result_int(pContext, poDS->GetRasterCount());
9172
0
    }
9173
0
    else
9174
0
        sqlite3_result_null(pContext);
9175
0
    VSIUnlink(osMemFileName);
9176
0
}
9177
9178
/************************************************************************/
9179
/*                       GPKG_GDAL_HasColorTable()                      */
9180
/************************************************************************/
9181
9182
static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9183
                                    sqlite3_value **argv)
9184
0
{
9185
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9186
0
    {
9187
0
        sqlite3_result_null(pContext);
9188
0
        return;
9189
0
    }
9190
9191
0
    CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9192
0
    auto poDS = std::unique_ptr<GDALDataset>(
9193
0
        GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9194
0
                          nullptr, nullptr, nullptr));
9195
0
    if (poDS != nullptr)
9196
0
    {
9197
0
        sqlite3_result_int(
9198
0
            pContext, poDS->GetRasterCount() == 1 &&
9199
0
                          poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9200
0
    }
9201
0
    else
9202
0
        sqlite3_result_null(pContext);
9203
0
    VSIUnlink(osMemFileName);
9204
0
}
9205
9206
/************************************************************************/
9207
/*                      GetRasterLayerDataset()                         */
9208
/************************************************************************/
9209
9210
GDALDataset *
9211
GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9212
0
{
9213
0
    const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9214
0
    if (oIter != m_oCachedRasterDS.end())
9215
0
        return oIter->second.get();
9216
9217
0
    auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9218
0
        (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9219
0
        GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9220
0
    if (!poDS)
9221
0
    {
9222
0
        return nullptr;
9223
0
    }
9224
0
    m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9225
0
    return m_oCachedRasterDS[pszLayerName].get();
9226
0
}
9227
9228
/************************************************************************/
9229
/*                   GPKG_gdal_get_layer_pixel_value()                  */
9230
/************************************************************************/
9231
9232
// NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9233
// and ogrgeopackagedatasource.cpp
9234
static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9235
                                            sqlite3_value **argv)
9236
0
{
9237
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9238
0
    {
9239
0
        CPLError(CE_Failure, CPLE_AppDefined,
9240
0
                 "Invalid arguments to gdal_get_layer_pixel_value()");
9241
0
        sqlite3_result_null(pContext);
9242
0
        return;
9243
0
    }
9244
9245
0
    const char *pszLayerName =
9246
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9247
9248
0
    GDALGeoPackageDataset *poGlobalDS =
9249
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9250
0
    auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9251
0
    if (!poDS)
9252
0
    {
9253
0
        sqlite3_result_null(pContext);
9254
0
        return;
9255
0
    }
9256
9257
0
    OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9258
0
                                          pContext, argc, argv, poDS);
9259
0
}
9260
9261
/************************************************************************/
9262
/*                       GPKG_ogr_layer_Extent()                        */
9263
/************************************************************************/
9264
9265
static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9266
                                  sqlite3_value **argv)
9267
0
{
9268
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9269
0
    {
9270
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9271
0
                 "ogr_layer_Extent");
9272
0
        sqlite3_result_null(pContext);
9273
0
        return;
9274
0
    }
9275
9276
0
    const char *pszLayerName =
9277
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9278
0
    GDALGeoPackageDataset *poDS =
9279
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9280
0
    OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9281
0
    if (!poLayer)
9282
0
    {
9283
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9284
0
                 "ogr_layer_Extent");
9285
0
        sqlite3_result_null(pContext);
9286
0
        return;
9287
0
    }
9288
9289
0
    if (poLayer->GetGeomType() == wkbNone)
9290
0
    {
9291
0
        sqlite3_result_null(pContext);
9292
0
        return;
9293
0
    }
9294
9295
0
    OGREnvelope sExtent;
9296
0
    if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9297
0
    {
9298
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9299
0
                 "ogr_layer_Extent");
9300
0
        sqlite3_result_null(pContext);
9301
0
        return;
9302
0
    }
9303
9304
0
    OGRPolygon oPoly;
9305
0
    auto poRing = std::make_unique<OGRLinearRing>();
9306
0
    poRing->addPoint(sExtent.MinX, sExtent.MinY);
9307
0
    poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9308
0
    poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9309
0
    poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9310
0
    poRing->addPoint(sExtent.MinX, sExtent.MinY);
9311
0
    oPoly.addRing(std::move(poRing));
9312
9313
0
    const auto poSRS = poLayer->GetSpatialRef();
9314
0
    const int nSRID = poDS->GetSrsId(poSRS);
9315
0
    size_t nBLOBDestLen = 0;
9316
0
    GByte *pabyDestBLOB =
9317
0
        GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9318
0
    if (!pabyDestBLOB)
9319
0
    {
9320
0
        sqlite3_result_null(pContext);
9321
0
        return;
9322
0
    }
9323
0
    sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9324
0
                        VSIFree);
9325
0
}
9326
9327
/************************************************************************/
9328
/*                      InstallSQLFunctions()                           */
9329
/************************************************************************/
9330
9331
#ifndef SQLITE_DETERMINISTIC
9332
#define SQLITE_DETERMINISTIC 0
9333
#endif
9334
9335
#ifndef SQLITE_INNOCUOUS
9336
#define SQLITE_INNOCUOUS 0
9337
#endif
9338
9339
#ifndef UTF8_INNOCUOUS
9340
#define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9341
#endif
9342
9343
void GDALGeoPackageDataset::InstallSQLFunctions()
9344
2.27k
{
9345
2.27k
    InitSpatialite();
9346
9347
    // Enable SpatiaLite 4.3 "amphibious" mode, i.e. that SpatiaLite functions
9348
    // that take geometries will accept GPKG encoded geometries without
9349
    // explicit conversion.
9350
    // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9351
    // error.
9352
2.27k
    sqlite3_exec(hDB, "SELECT EnableGpkgAmphibiousMode()", nullptr, nullptr,
9353
2.27k
                 nullptr);
9354
9355
    /* Used by RTree Spatial Index Extension */
9356
2.27k
    sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9357
2.27k
                            OGRGeoPackageSTMinX, nullptr, nullptr);
9358
2.27k
    sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9359
2.27k
                            OGRGeoPackageSTMinY, nullptr, nullptr);
9360
2.27k
    sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9361
2.27k
                            OGRGeoPackageSTMaxX, nullptr, nullptr);
9362
2.27k
    sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9363
2.27k
                            OGRGeoPackageSTMaxY, nullptr, nullptr);
9364
2.27k
    sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9365
2.27k
                            OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9366
9367
    /* Used by Geometry Type Triggers Extension */
9368
2.27k
    sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9369
2.27k
                            OGRGeoPackageSTGeometryType, nullptr, nullptr);
9370
2.27k
    sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9371
2.27k
                            nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9372
2.27k
                            nullptr);
9373
9374
    /* Used by Geometry SRS ID Triggers Extension */
9375
2.27k
    sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9376
2.27k
                            OGRGeoPackageSTSRID, nullptr, nullptr);
9377
9378
    /* Spatialite-like functions */
9379
2.27k
    sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9380
2.27k
                            OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9381
2.27k
    sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9382
2.27k
                            OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9383
2.27k
    sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9384
2.27k
                            OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9385
9386
    // HSTORE functions
9387
2.27k
    sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9388
2.27k
                            GPKG_hstore_get_value, nullptr, nullptr);
9389
9390
    // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9391
2.27k
    sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9392
2.27k
                            OGRGeoPackageTransform, nullptr, nullptr);
9393
2.27k
    sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9394
2.27k
                            OGRGeoPackageTransform, nullptr, nullptr);
9395
2.27k
    sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9396
2.27k
                            OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9397
9398
2.27k
    sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9399
2.27k
                            OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9400
2.27k
                            nullptr, nullptr);
9401
2.27k
    sqlite3_create_function(
9402
2.27k
        hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9403
2.27k
        OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9404
9405
2.27k
    sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9406
2.27k
                            OGRGeoPackageSTEnvelopesIntersects, nullptr,
9407
2.27k
                            nullptr);
9408
2.27k
    sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9409
2.27k
                            nullptr, OGRGeoPackageSTEnvelopesIntersects,
9410
2.27k
                            nullptr, nullptr);
9411
9412
    // Implementation that directly hacks the GeoPackage geometry blob header
9413
2.27k
    sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9414
2.27k
                            OGRGeoPackageSetSRID, nullptr, nullptr);
9415
9416
    // GDAL specific function
9417
2.27k
    sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9418
2.27k
                            OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9419
9420
    // May be used by ogrmerge.py
9421
2.27k
    sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9422
2.27k
                            this, OGRGeoPackageRegisterGeometryExtension,
9423
2.27k
                            nullptr, nullptr);
9424
9425
2.27k
    if (OGRGeometryFactory::haveGEOS())
9426
0
    {
9427
0
        sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9428
0
                                OGRGeoPackageSTMakeValid, nullptr, nullptr);
9429
0
    }
9430
9431
2.27k
    sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9432
2.27k
                            OGRGeoPackageLengthOrGeodesicLength, nullptr,
9433
2.27k
                            nullptr);
9434
2.27k
    sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9435
2.27k
                            OGRGeoPackageLengthOrGeodesicLength, nullptr,
9436
2.27k
                            nullptr);
9437
9438
2.27k
    sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9439
2.27k
                            OGRGeoPackageSTArea, nullptr, nullptr);
9440
2.27k
    sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9441
2.27k
                            OGRGeoPackageGeodesicArea, nullptr, nullptr);
9442
9443
    // Debug functions
9444
2.27k
    if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9445
0
    {
9446
0
        sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9447
0
                                SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9448
0
                                GPKG_GDAL_GetMimeType, nullptr, nullptr);
9449
0
        sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9450
0
                                SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9451
0
                                GPKG_GDAL_GetBandCount, nullptr, nullptr);
9452
0
        sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9453
0
                                SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9454
0
                                GPKG_GDAL_HasColorTable, nullptr, nullptr);
9455
0
    }
9456
9457
2.27k
    sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9458
2.27k
                            this, GPKG_gdal_get_layer_pixel_value, nullptr,
9459
2.27k
                            nullptr);
9460
2.27k
    sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9461
2.27k
                            this, GPKG_gdal_get_layer_pixel_value, nullptr,
9462
2.27k
                            nullptr);
9463
9464
    // Function from VirtualOGR
9465
2.27k
    sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9466
2.27k
                            GPKG_ogr_layer_Extent, nullptr, nullptr);
9467
9468
2.27k
    m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9469
2.27k
}
9470
9471
/************************************************************************/
9472
/*                         OpenOrCreateDB()                             */
9473
/************************************************************************/
9474
9475
bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9476
794
{
9477
794
    const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9478
794
        flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9479
794
        /*bLoadExtensions=*/true);
9480
794
    if (!bSuccess)
9481
286
        return false;
9482
9483
    // Turning on recursive_triggers is needed so that DELETE triggers fire
9484
    // in a INSERT OR REPLACE statement. In particular this is needed to
9485
    // make sure gpkg_ogr_contents.feature_count is properly updated.
9486
508
    SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9487
9488
508
    InstallSQLFunctions();
9489
9490
508
    const char *pszSqlitePragma =
9491
508
        CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9492
508
    OGRErr eErr = OGRERR_NONE;
9493
508
    if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9494
        // Older sqlite versions don't have this pragma
9495
508
        SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9496
508
        eErr == OGRERR_NONE)
9497
0
    {
9498
0
        bool bNeedsTrustedSchema = false;
9499
9500
        // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9501
        // able to use the RTree from triggers, which is only needed when
9502
        // modifying the RTree.
9503
0
        if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9504
0
             (flags & SQLITE_OPEN_CREATE) != 0) &&
9505
0
            OGRSQLiteRTreeRequiresTrustedSchemaOn())
9506
0
        {
9507
0
            bNeedsTrustedSchema = true;
9508
0
        }
9509
9510
#ifdef HAVE_SPATIALITE
9511
        // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9512
        if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9513
            SQLGetInteger(
9514
                hDB,
9515
                "SELECT 1 FROM gpkg_extensions WHERE "
9516
                "extension_name ='gdal_spatialite_computed_geom_column'",
9517
                nullptr) == 1 &&
9518
            SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9519
        {
9520
            bNeedsTrustedSchema = true;
9521
        }
9522
#endif
9523
9524
0
        if (bNeedsTrustedSchema)
9525
0
        {
9526
0
            CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9527
0
            SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9528
0
        }
9529
0
    }
9530
9531
508
    const char *pszPreludeStatements =
9532
508
        CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9533
508
    if (pszPreludeStatements)
9534
0
    {
9535
0
        if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9536
0
            return false;
9537
0
    }
9538
9539
508
    return true;
9540
508
}
9541
9542
/************************************************************************/
9543
/*                   GetLayerWithGetSpatialWhereByName()                */
9544
/************************************************************************/
9545
9546
std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9547
GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9548
0
{
9549
0
    OGRGeoPackageLayer *poRet =
9550
0
        cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9551
0
    return std::pair(poRet, poRet);
9552
0
}
9553
9554
/************************************************************************/
9555
/*                       CommitTransaction()                            */
9556
/************************************************************************/
9557
9558
OGRErr GDALGeoPackageDataset::CommitTransaction()
9559
9560
120k
{
9561
120k
    if (m_nSoftTransactionLevel == 1)
9562
120k
    {
9563
120k
        FlushMetadata();
9564
120k
        for (auto &poLayer : m_apoLayers)
9565
1.44M
        {
9566
1.44M
            poLayer->DoJobAtTransactionCommit();
9567
1.44M
        }
9568
120k
    }
9569
9570
120k
    return OGRSQLiteBaseDataSource::CommitTransaction();
9571
120k
}
9572
9573
/************************************************************************/
9574
/*                     RollbackTransaction()                            */
9575
/************************************************************************/
9576
9577
OGRErr GDALGeoPackageDataset::RollbackTransaction()
9578
9579
0
{
9580
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
9581
0
    std::vector<bool> abAddTriggers;
9582
0
    std::vector<bool> abTriggersDeletedInTransaction;
9583
0
#endif
9584
0
    if (m_nSoftTransactionLevel == 1)
9585
0
    {
9586
0
        FlushMetadata();
9587
0
        for (auto &poLayer : m_apoLayers)
9588
0
        {
9589
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
9590
0
            abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9591
0
            abTriggersDeletedInTransaction.push_back(
9592
0
                poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9593
0
            poLayer->SetAddOGRFeatureCountTriggers(false);
9594
0
#endif
9595
0
            poLayer->DoJobAtTransactionRollback();
9596
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
9597
0
            poLayer->DisableFeatureCount();
9598
0
#endif
9599
0
        }
9600
0
    }
9601
9602
0
    const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9603
9604
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
9605
0
    if (!abAddTriggers.empty())
9606
0
    {
9607
0
        for (size_t i = 0; i < m_apoLayers.size(); ++i)
9608
0
        {
9609
0
            auto &poLayer = m_apoLayers[i];
9610
0
            if (abTriggersDeletedInTransaction[i])
9611
0
            {
9612
0
                poLayer->SetOGRFeatureCountTriggersEnabled(true);
9613
0
            }
9614
0
            else
9615
0
            {
9616
0
                poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9617
0
            }
9618
0
        }
9619
0
    }
9620
0
#endif
9621
0
    return eErr;
9622
0
}
9623
9624
/************************************************************************/
9625
/*                       GetGeometryTypeString()                        */
9626
/************************************************************************/
9627
9628
const char *
9629
GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9630
1.14k
{
9631
1.14k
    const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9632
1.14k
    if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9633
1.14k
        CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9634
0
    {
9635
0
        pszGPKGGeomType = "GEOMCOLLECTION";
9636
0
    }
9637
1.14k
    return pszGPKGGeomType;
9638
1.14k
}
9639
9640
/************************************************************************/
9641
/*                           GetFieldDomainNames()                      */
9642
/************************************************************************/
9643
9644
std::vector<std::string>
9645
GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9646
0
{
9647
0
    if (!HasDataColumnConstraintsTable())
9648
0
        return std::vector<std::string>();
9649
9650
0
    std::vector<std::string> oDomainNamesList;
9651
9652
0
    std::unique_ptr<SQLResult> oResultTable;
9653
0
    {
9654
0
        std::string osSQL =
9655
0
            "SELECT DISTINCT constraint_name "
9656
0
            "FROM gpkg_data_column_constraints "
9657
0
            "WHERE constraint_name NOT LIKE '_%_domain_description' "
9658
0
            "ORDER BY constraint_name "
9659
0
            "LIMIT 10000"  // to avoid denial of service
9660
0
            ;
9661
0
        oResultTable = SQLQuery(hDB, osSQL.c_str());
9662
0
        if (!oResultTable)
9663
0
            return oDomainNamesList;
9664
0
    }
9665
9666
0
    if (oResultTable->RowCount() == 10000)
9667
0
    {
9668
0
        CPLError(CE_Warning, CPLE_AppDefined,
9669
0
                 "Number of rows returned for field domain names has been "
9670
0
                 "truncated.");
9671
0
    }
9672
0
    else if (oResultTable->RowCount() > 0)
9673
0
    {
9674
0
        oDomainNamesList.reserve(oResultTable->RowCount());
9675
0
        for (int i = 0; i < oResultTable->RowCount(); i++)
9676
0
        {
9677
0
            const char *pszConstraintName = oResultTable->GetValue(0, i);
9678
0
            if (!pszConstraintName)
9679
0
                continue;
9680
9681
0
            oDomainNamesList.emplace_back(pszConstraintName);
9682
0
        }
9683
0
    }
9684
9685
0
    return oDomainNamesList;
9686
0
}
9687
9688
/************************************************************************/
9689
/*                           GetFieldDomain()                           */
9690
/************************************************************************/
9691
9692
const OGRFieldDomain *
9693
GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9694
0
{
9695
0
    const auto baseRet = GDALDataset::GetFieldDomain(name);
9696
0
    if (baseRet)
9697
0
        return baseRet;
9698
9699
0
    if (!HasDataColumnConstraintsTable())
9700
0
        return nullptr;
9701
9702
0
    const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9703
0
    const char *min_is_inclusive =
9704
0
        bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9705
0
    const char *max_is_inclusive =
9706
0
        bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9707
9708
0
    std::unique_ptr<SQLResult> oResultTable;
9709
    // Note: for coded domains, we use a little trick by using a dummy
9710
    // _{domainname}_domain_description enum that has a single entry whose
9711
    // description is the description of the main domain.
9712
0
    {
9713
0
        char *pszSQL = sqlite3_mprintf(
9714
0
            "SELECT constraint_type, value, min, %s, "
9715
0
            "max, %s, description, constraint_name "
9716
0
            "FROM gpkg_data_column_constraints "
9717
0
            "WHERE constraint_name IN ('%q', "
9718
0
            "'_%q_domain_description') "
9719
0
            "AND length(constraint_type) < 100 "  // to
9720
                                                  // avoid
9721
                                                  // denial
9722
                                                  // of
9723
                                                  // service
9724
0
            "AND (value IS NULL OR length(value) < "
9725
0
            "10000) "  // to avoid denial
9726
                       // of service
9727
0
            "AND (description IS NULL OR "
9728
0
            "length(description) < 10000) "  // to
9729
                                             // avoid
9730
                                             // denial
9731
                                             // of
9732
                                             // service
9733
0
            "ORDER BY value "
9734
0
            "LIMIT 10000",  // to avoid denial of
9735
                            // service
9736
0
            min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9737
0
        oResultTable = SQLQuery(hDB, pszSQL);
9738
0
        sqlite3_free(pszSQL);
9739
0
        if (!oResultTable)
9740
0
            return nullptr;
9741
0
    }
9742
0
    if (oResultTable->RowCount() == 0)
9743
0
    {
9744
0
        return nullptr;
9745
0
    }
9746
0
    if (oResultTable->RowCount() == 10000)
9747
0
    {
9748
0
        CPLError(CE_Warning, CPLE_AppDefined,
9749
0
                 "Number of rows returned for field domain %s has been "
9750
0
                 "truncated.",
9751
0
                 name.c_str());
9752
0
    }
9753
9754
    // Try to find the field domain data type from fields that implement it
9755
0
    int nFieldType = -1;
9756
0
    OGRFieldSubType eSubType = OFSTNone;
9757
0
    if (HasDataColumnsTable())
9758
0
    {
9759
0
        char *pszSQL = sqlite3_mprintf(
9760
0
            "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9761
0
            "constraint_name = '%q' LIMIT 10",
9762
0
            name.c_str());
9763
0
        auto oResultTable2 = SQLQuery(hDB, pszSQL);
9764
0
        sqlite3_free(pszSQL);
9765
0
        if (oResultTable2 && oResultTable2->RowCount() >= 1)
9766
0
        {
9767
0
            for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9768
0
                 iRecord++)
9769
0
            {
9770
0
                const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9771
0
                const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9772
0
                if (pszTableName == nullptr || pszColumnName == nullptr)
9773
0
                    continue;
9774
0
                OGRLayer *poLayer =
9775
0
                    const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9776
0
                        pszTableName);
9777
0
                if (poLayer)
9778
0
                {
9779
0
                    const auto poFDefn = poLayer->GetLayerDefn();
9780
0
                    int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9781
0
                    if (nIdx >= 0)
9782
0
                    {
9783
0
                        const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9784
0
                        const auto eType = poFieldDefn->GetType();
9785
0
                        if (nFieldType < 0)
9786
0
                        {
9787
0
                            nFieldType = eType;
9788
0
                            eSubType = poFieldDefn->GetSubType();
9789
0
                        }
9790
0
                        else if ((eType == OFTInteger64 || eType == OFTReal) &&
9791
0
                                 nFieldType == OFTInteger)
9792
0
                        {
9793
                            // ok
9794
0
                        }
9795
0
                        else if (eType == OFTInteger &&
9796
0
                                 (nFieldType == OFTInteger64 ||
9797
0
                                  nFieldType == OFTReal))
9798
0
                        {
9799
0
                            nFieldType = OFTInteger;
9800
0
                            eSubType = OFSTNone;
9801
0
                        }
9802
0
                        else if (nFieldType != eType)
9803
0
                        {
9804
0
                            nFieldType = -1;
9805
0
                            eSubType = OFSTNone;
9806
0
                            break;
9807
0
                        }
9808
0
                    }
9809
0
                }
9810
0
            }
9811
0
        }
9812
0
    }
9813
9814
0
    std::unique_ptr<OGRFieldDomain> poDomain;
9815
0
    std::vector<OGRCodedValue> asValues;
9816
0
    bool error = false;
9817
0
    CPLString osLastConstraintType;
9818
0
    int nFieldTypeFromEnumCode = -1;
9819
0
    std::string osConstraintDescription;
9820
0
    std::string osDescrConstraintName("_");
9821
0
    osDescrConstraintName += name;
9822
0
    osDescrConstraintName += "_domain_description";
9823
0
    for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9824
0
    {
9825
0
        const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9826
0
        if (pszConstraintType == nullptr)
9827
0
            continue;
9828
0
        const char *pszValue = oResultTable->GetValue(1, iRecord);
9829
0
        const char *pszMin = oResultTable->GetValue(2, iRecord);
9830
0
        const bool bIsMinIncluded =
9831
0
            oResultTable->GetValueAsInteger(3, iRecord) == 1;
9832
0
        const char *pszMax = oResultTable->GetValue(4, iRecord);
9833
0
        const bool bIsMaxIncluded =
9834
0
            oResultTable->GetValueAsInteger(5, iRecord) == 1;
9835
0
        const char *pszDescription = oResultTable->GetValue(6, iRecord);
9836
0
        const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9837
9838
0
        if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9839
0
        {
9840
0
            CPLError(CE_Failure, CPLE_AppDefined,
9841
0
                     "Only constraint of type 'enum' can have multiple rows");
9842
0
            error = true;
9843
0
            break;
9844
0
        }
9845
9846
0
        if (strcmp(pszConstraintType, "enum") == 0)
9847
0
        {
9848
0
            if (pszValue == nullptr)
9849
0
            {
9850
0
                CPLError(CE_Failure, CPLE_AppDefined,
9851
0
                         "NULL in 'value' column of enumeration");
9852
0
                error = true;
9853
0
                break;
9854
0
            }
9855
0
            if (osDescrConstraintName == pszConstraintName)
9856
0
            {
9857
0
                if (pszDescription)
9858
0
                {
9859
0
                    osConstraintDescription = pszDescription;
9860
0
                }
9861
0
                continue;
9862
0
            }
9863
0
            if (asValues.empty())
9864
0
            {
9865
0
                asValues.reserve(oResultTable->RowCount() + 1);
9866
0
            }
9867
0
            OGRCodedValue cv;
9868
            // intended: the 'value' column in GPKG is actually the code
9869
0
            cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9870
0
            if (cv.pszCode == nullptr)
9871
0
            {
9872
0
                error = true;
9873
0
                break;
9874
0
            }
9875
0
            if (pszDescription)
9876
0
            {
9877
0
                cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
9878
0
                if (cv.pszValue == nullptr)
9879
0
                {
9880
0
                    VSIFree(cv.pszCode);
9881
0
                    error = true;
9882
0
                    break;
9883
0
                }
9884
0
            }
9885
0
            else
9886
0
            {
9887
0
                cv.pszValue = nullptr;
9888
0
            }
9889
9890
            // If we can't get the data type from field definition, guess it
9891
            // from code.
9892
0
            if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
9893
0
            {
9894
0
                switch (CPLGetValueType(cv.pszCode))
9895
0
                {
9896
0
                    case CPL_VALUE_INTEGER:
9897
0
                    {
9898
0
                        if (nFieldTypeFromEnumCode != OFTReal &&
9899
0
                            nFieldTypeFromEnumCode != OFTInteger64)
9900
0
                        {
9901
0
                            const auto nVal = CPLAtoGIntBig(cv.pszCode);
9902
0
                            if (nVal < std::numeric_limits<int>::min() ||
9903
0
                                nVal > std::numeric_limits<int>::max())
9904
0
                            {
9905
0
                                nFieldTypeFromEnumCode = OFTInteger64;
9906
0
                            }
9907
0
                            else
9908
0
                            {
9909
0
                                nFieldTypeFromEnumCode = OFTInteger;
9910
0
                            }
9911
0
                        }
9912
0
                        break;
9913
0
                    }
9914
9915
0
                    case CPL_VALUE_REAL:
9916
0
                        nFieldTypeFromEnumCode = OFTReal;
9917
0
                        break;
9918
9919
0
                    case CPL_VALUE_STRING:
9920
0
                        nFieldTypeFromEnumCode = OFTString;
9921
0
                        break;
9922
0
                }
9923
0
            }
9924
9925
0
            asValues.emplace_back(cv);
9926
0
        }
9927
0
        else if (strcmp(pszConstraintType, "range") == 0)
9928
0
        {
9929
0
            OGRField sMin;
9930
0
            OGRField sMax;
9931
0
            OGR_RawField_SetUnset(&sMin);
9932
0
            OGR_RawField_SetUnset(&sMax);
9933
0
            if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
9934
0
                nFieldType = OFTReal;
9935
0
            if (pszMin != nullptr &&
9936
0
                CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
9937
0
            {
9938
0
                if (nFieldType == OFTInteger)
9939
0
                    sMin.Integer = atoi(pszMin);
9940
0
                else if (nFieldType == OFTInteger64)
9941
0
                    sMin.Integer64 = CPLAtoGIntBig(pszMin);
9942
0
                else /* if( nFieldType == OFTReal ) */
9943
0
                    sMin.Real = CPLAtof(pszMin);
9944
0
            }
9945
0
            if (pszMax != nullptr &&
9946
0
                CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
9947
0
            {
9948
0
                if (nFieldType == OFTInteger)
9949
0
                    sMax.Integer = atoi(pszMax);
9950
0
                else if (nFieldType == OFTInteger64)
9951
0
                    sMax.Integer64 = CPLAtoGIntBig(pszMax);
9952
0
                else /* if( nFieldType == OFTReal ) */
9953
0
                    sMax.Real = CPLAtof(pszMax);
9954
0
            }
9955
0
            poDomain = std::make_unique<OGRRangeFieldDomain>(
9956
0
                name, pszDescription ? pszDescription : "",
9957
0
                static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
9958
0
                bIsMinIncluded, sMax, bIsMaxIncluded);
9959
0
        }
9960
0
        else if (strcmp(pszConstraintType, "glob") == 0)
9961
0
        {
9962
0
            if (pszValue == nullptr)
9963
0
            {
9964
0
                CPLError(CE_Failure, CPLE_AppDefined,
9965
0
                         "NULL in 'value' column of glob");
9966
0
                error = true;
9967
0
                break;
9968
0
            }
9969
0
            if (nFieldType < 0)
9970
0
                nFieldType = OFTString;
9971
0
            poDomain = std::make_unique<OGRGlobFieldDomain>(
9972
0
                name, pszDescription ? pszDescription : "",
9973
0
                static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
9974
0
        }
9975
0
        else
9976
0
        {
9977
0
            CPLError(CE_Failure, CPLE_AppDefined,
9978
0
                     "Unhandled constraint_type: %s", pszConstraintType);
9979
0
            error = true;
9980
0
            break;
9981
0
        }
9982
9983
0
        osLastConstraintType = pszConstraintType;
9984
0
    }
9985
9986
0
    if (!asValues.empty())
9987
0
    {
9988
0
        if (nFieldType < 0)
9989
0
            nFieldType = nFieldTypeFromEnumCode;
9990
0
        poDomain = std::make_unique<OGRCodedFieldDomain>(
9991
0
            name, osConstraintDescription,
9992
0
            static_cast<OGRFieldType>(nFieldType), eSubType,
9993
0
            std::move(asValues));
9994
0
    }
9995
9996
0
    if (error)
9997
0
    {
9998
0
        return nullptr;
9999
0
    }
10000
10001
0
    m_oMapFieldDomains[name] = std::move(poDomain);
10002
0
    return GDALDataset::GetFieldDomain(name);
10003
0
}
10004
10005
/************************************************************************/
10006
/*                           AddFieldDomain()                           */
10007
/************************************************************************/
10008
10009
bool GDALGeoPackageDataset::AddFieldDomain(
10010
    std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10011
0
{
10012
0
    const std::string domainName(domain->GetName());
10013
0
    if (!GetUpdate())
10014
0
    {
10015
0
        CPLError(CE_Failure, CPLE_NotSupported,
10016
0
                 "AddFieldDomain() not supported on read-only dataset");
10017
0
        return false;
10018
0
    }
10019
0
    if (GetFieldDomain(domainName) != nullptr)
10020
0
    {
10021
0
        failureReason = "A domain of identical name already exists";
10022
0
        return false;
10023
0
    }
10024
0
    if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10025
0
        return false;
10026
10027
0
    const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10028
0
    const char *min_is_inclusive =
10029
0
        bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10030
0
    const char *max_is_inclusive =
10031
0
        bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10032
10033
0
    const auto &osDescription = domain->GetDescription();
10034
0
    switch (domain->GetDomainType())
10035
0
    {
10036
0
        case OFDT_CODED:
10037
0
        {
10038
0
            const auto poCodedDomain =
10039
0
                cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10040
0
            if (!osDescription.empty())
10041
0
            {
10042
                // We use a little trick by using a dummy
10043
                // _{domainname}_domain_description enum that has a single
10044
                // entry whose description is the description of the main
10045
                // domain.
10046
0
                char *pszSQL = sqlite3_mprintf(
10047
0
                    "INSERT INTO gpkg_data_column_constraints ("
10048
0
                    "constraint_name, constraint_type, value, "
10049
0
                    "min, %s, max, %s, "
10050
0
                    "description) VALUES ("
10051
0
                    "'_%q_domain_description', 'enum', '', NULL, NULL, NULL, "
10052
0
                    "NULL, %Q)",
10053
0
                    min_is_inclusive, max_is_inclusive, domainName.c_str(),
10054
0
                    osDescription.c_str());
10055
0
                CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10056
0
                sqlite3_free(pszSQL);
10057
0
            }
10058
0
            const auto &enumeration = poCodedDomain->GetEnumeration();
10059
0
            for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10060
0
            {
10061
0
                char *pszSQL = sqlite3_mprintf(
10062
0
                    "INSERT INTO gpkg_data_column_constraints ("
10063
0
                    "constraint_name, constraint_type, value, "
10064
0
                    "min, %s, max, %s, "
10065
0
                    "description) VALUES ("
10066
0
                    "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10067
0
                    min_is_inclusive, max_is_inclusive, domainName.c_str(),
10068
0
                    enumeration[i].pszCode, enumeration[i].pszValue);
10069
0
                bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10070
0
                sqlite3_free(pszSQL);
10071
0
                if (!ok)
10072
0
                    return false;
10073
0
            }
10074
0
            break;
10075
0
        }
10076
10077
0
        case OFDT_RANGE:
10078
0
        {
10079
0
            const auto poRangeDomain =
10080
0
                cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10081
0
            const auto eFieldType = poRangeDomain->GetFieldType();
10082
0
            if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10083
0
                eFieldType != OFTReal)
10084
0
            {
10085
0
                failureReason = "Only range domains of numeric type are "
10086
0
                                "supported in GeoPackage";
10087
0
                return false;
10088
0
            }
10089
10090
0
            double dfMin = -std::numeric_limits<double>::infinity();
10091
0
            double dfMax = std::numeric_limits<double>::infinity();
10092
0
            bool bMinIsInclusive = true;
10093
0
            const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10094
0
            bool bMaxIsInclusive = true;
10095
0
            const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10096
0
            if (eFieldType == OFTInteger)
10097
0
            {
10098
0
                if (!OGR_RawField_IsUnset(&sMin))
10099
0
                    dfMin = sMin.Integer;
10100
0
                if (!OGR_RawField_IsUnset(&sMax))
10101
0
                    dfMax = sMax.Integer;
10102
0
            }
10103
0
            else if (eFieldType == OFTInteger64)
10104
0
            {
10105
0
                if (!OGR_RawField_IsUnset(&sMin))
10106
0
                    dfMin = static_cast<double>(sMin.Integer64);
10107
0
                if (!OGR_RawField_IsUnset(&sMax))
10108
0
                    dfMax = static_cast<double>(sMax.Integer64);
10109
0
            }
10110
0
            else /* if( eFieldType == OFTReal ) */
10111
0
            {
10112
0
                if (!OGR_RawField_IsUnset(&sMin))
10113
0
                    dfMin = sMin.Real;
10114
0
                if (!OGR_RawField_IsUnset(&sMax))
10115
0
                    dfMax = sMax.Real;
10116
0
            }
10117
10118
0
            sqlite3_stmt *hInsertStmt = nullptr;
10119
0
            const char *pszSQL =
10120
0
                CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10121
0
                           "constraint_name, constraint_type, value, "
10122
0
                           "min, %s, max, %s, "
10123
0
                           "description) VALUES ("
10124
0
                           "?, 'range', NULL, ?, ?, ?, ?, ?)",
10125
0
                           min_is_inclusive, max_is_inclusive);
10126
0
            if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10127
0
                SQLITE_OK)
10128
0
            {
10129
0
                return false;
10130
0
            }
10131
0
            sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10132
0
                              static_cast<int>(domainName.size()),
10133
0
                              SQLITE_TRANSIENT);
10134
0
            sqlite3_bind_double(hInsertStmt, 2, dfMin);
10135
0
            sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10136
0
            sqlite3_bind_double(hInsertStmt, 4, dfMax);
10137
0
            sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10138
0
            if (osDescription.empty())
10139
0
            {
10140
0
                sqlite3_bind_null(hInsertStmt, 6);
10141
0
            }
10142
0
            else
10143
0
            {
10144
0
                sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10145
0
                                  static_cast<int>(osDescription.size()),
10146
0
                                  SQLITE_TRANSIENT);
10147
0
            }
10148
0
            const int sqlite_err = sqlite3_step(hInsertStmt);
10149
0
            sqlite3_finalize(hInsertStmt);
10150
0
            if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10151
0
            {
10152
0
                CPLError(CE_Failure, CPLE_AppDefined,
10153
0
                         "failed to execute insertion '%s': %s", pszSQL,
10154
0
                         sqlite3_errmsg(hDB));
10155
0
                return false;
10156
0
            }
10157
10158
0
            break;
10159
0
        }
10160
10161
0
        case OFDT_GLOB:
10162
0
        {
10163
0
            const auto poGlobDomain =
10164
0
                cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10165
0
            char *pszSQL = sqlite3_mprintf(
10166
0
                "INSERT INTO gpkg_data_column_constraints ("
10167
0
                "constraint_name, constraint_type, value, "
10168
0
                "min, %s, max, %s, "
10169
0
                "description) VALUES ("
10170
0
                "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10171
0
                min_is_inclusive, max_is_inclusive, domainName.c_str(),
10172
0
                poGlobDomain->GetGlob().c_str(),
10173
0
                osDescription.empty() ? nullptr : osDescription.c_str());
10174
0
            bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10175
0
            sqlite3_free(pszSQL);
10176
0
            if (!ok)
10177
0
                return false;
10178
10179
0
            break;
10180
0
        }
10181
0
    }
10182
10183
0
    m_oMapFieldDomains[domainName] = std::move(domain);
10184
0
    return true;
10185
0
}
10186
10187
/************************************************************************/
10188
/*                          AddRelationship()                           */
10189
/************************************************************************/
10190
10191
bool GDALGeoPackageDataset::AddRelationship(
10192
    std::unique_ptr<GDALRelationship> &&relationship,
10193
    std::string &failureReason)
10194
0
{
10195
0
    if (!GetUpdate())
10196
0
    {
10197
0
        CPLError(CE_Failure, CPLE_NotSupported,
10198
0
                 "AddRelationship() not supported on read-only dataset");
10199
0
        return false;
10200
0
    }
10201
10202
0
    const std::string osRelationshipName = GenerateNameForRelationship(
10203
0
        relationship->GetLeftTableName().c_str(),
10204
0
        relationship->GetRightTableName().c_str(),
10205
0
        relationship->GetRelatedTableType().c_str());
10206
    // sanity checks
10207
0
    if (GetRelationship(osRelationshipName) != nullptr)
10208
0
    {
10209
0
        failureReason = "A relationship of identical name already exists";
10210
0
        return false;
10211
0
    }
10212
10213
0
    if (!ValidateRelationship(relationship.get(), failureReason))
10214
0
    {
10215
0
        return false;
10216
0
    }
10217
10218
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10219
0
    {
10220
0
        return false;
10221
0
    }
10222
0
    if (!CreateRelationsTableIfNecessary())
10223
0
    {
10224
0
        failureReason = "Could not create gpkgext_relations table";
10225
0
        return false;
10226
0
    }
10227
0
    if (SQLGetInteger(GetDB(),
10228
0
                      "SELECT 1 FROM gpkg_extensions WHERE "
10229
0
                      "table_name = 'gpkgext_relations'",
10230
0
                      nullptr) != 1)
10231
0
    {
10232
0
        if (OGRERR_NONE !=
10233
0
            SQLCommand(
10234
0
                GetDB(),
10235
0
                "INSERT INTO gpkg_extensions "
10236
0
                "(table_name,column_name,extension_name,definition,scope) "
10237
0
                "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10238
0
                "'http://www.geopackage.org/18-000.html', "
10239
0
                "'read-write')"))
10240
0
        {
10241
0
            failureReason =
10242
0
                "Could not create gpkg_extensions entry for gpkgext_relations";
10243
0
            return false;
10244
0
        }
10245
0
    }
10246
10247
0
    const std::string &osLeftTableName = relationship->GetLeftTableName();
10248
0
    const std::string &osRightTableName = relationship->GetRightTableName();
10249
0
    const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10250
0
    const auto &aosRightTableFields = relationship->GetRightTableFields();
10251
10252
0
    std::string osRelatedTableType = relationship->GetRelatedTableType();
10253
0
    if (osRelatedTableType.empty())
10254
0
    {
10255
0
        osRelatedTableType = "features";
10256
0
    }
10257
10258
    // generate mapping table if not set
10259
0
    CPLString osMappingTableName = relationship->GetMappingTableName();
10260
0
    if (osMappingTableName.empty())
10261
0
    {
10262
0
        int nIndex = 1;
10263
0
        osMappingTableName = osLeftTableName + "_" + osRightTableName;
10264
0
        while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10265
0
        {
10266
0
            nIndex += 1;
10267
0
            osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10268
0
                                      osRightTableName.c_str(), nIndex);
10269
0
        }
10270
10271
        // determine whether base/related keys are unique
10272
0
        bool bBaseKeyIsUnique = false;
10273
0
        {
10274
0
            const std::set<std::string> uniqueBaseFieldsUC =
10275
0
                SQLGetUniqueFieldUCConstraints(GetDB(),
10276
0
                                               osLeftTableName.c_str());
10277
0
            if (uniqueBaseFieldsUC.find(
10278
0
                    CPLString(aosLeftTableFields[0]).toupper()) !=
10279
0
                uniqueBaseFieldsUC.end())
10280
0
            {
10281
0
                bBaseKeyIsUnique = true;
10282
0
            }
10283
0
        }
10284
0
        bool bRelatedKeyIsUnique = false;
10285
0
        {
10286
0
            const std::set<std::string> uniqueRelatedFieldsUC =
10287
0
                SQLGetUniqueFieldUCConstraints(GetDB(),
10288
0
                                               osRightTableName.c_str());
10289
0
            if (uniqueRelatedFieldsUC.find(
10290
0
                    CPLString(aosRightTableFields[0]).toupper()) !=
10291
0
                uniqueRelatedFieldsUC.end())
10292
0
            {
10293
0
                bRelatedKeyIsUnique = true;
10294
0
            }
10295
0
        }
10296
10297
        // create mapping table
10298
10299
0
        std::string osBaseIdDefinition = "base_id INTEGER";
10300
0
        if (bBaseKeyIsUnique)
10301
0
        {
10302
0
            char *pszSQL = sqlite3_mprintf(
10303
0
                " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10304
0
                "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10305
0
                "DEFERRED",
10306
0
                osMappingTableName.c_str(), osLeftTableName.c_str(),
10307
0
                aosLeftTableFields[0].c_str());
10308
0
            osBaseIdDefinition += pszSQL;
10309
0
            sqlite3_free(pszSQL);
10310
0
        }
10311
10312
0
        std::string osRelatedIdDefinition = "related_id INTEGER";
10313
0
        if (bRelatedKeyIsUnique)
10314
0
        {
10315
0
            char *pszSQL = sqlite3_mprintf(
10316
0
                " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10317
0
                "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10318
0
                "DEFERRED",
10319
0
                osMappingTableName.c_str(), osRightTableName.c_str(),
10320
0
                aosRightTableFields[0].c_str());
10321
0
            osRelatedIdDefinition += pszSQL;
10322
0
            sqlite3_free(pszSQL);
10323
0
        }
10324
10325
0
        char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10326
0
                                       "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10327
0
                                       "%s, %s);",
10328
0
                                       osMappingTableName.c_str(),
10329
0
                                       osBaseIdDefinition.c_str(),
10330
0
                                       osRelatedIdDefinition.c_str());
10331
0
        OGRErr eErr = SQLCommand(hDB, pszSQL);
10332
0
        sqlite3_free(pszSQL);
10333
0
        if (eErr != OGRERR_NONE)
10334
0
        {
10335
0
            failureReason =
10336
0
                ("Could not create mapping table " + osMappingTableName)
10337
0
                    .c_str();
10338
0
            return false;
10339
0
        }
10340
10341
        /*
10342
         * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10343
         * The related tables extension explicitly states that the mapping table should only be
10344
         * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10345
         * https://github.com/opengeospatial/geopackage/issues/679).
10346
         *
10347
         * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10348
         * visible to some clients (eg ESRI software only allows opening tables that are present
10349
         * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10350
         *
10351
         * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10352
         */
10353
0
        pszSQL = sqlite3_mprintf(
10354
0
            "INSERT INTO gpkg_contents "
10355
0
            "(table_name,data_type,identifier,description,last_change,srs_id) "
10356
0
            "VALUES "
10357
0
            "('%q','attributes','%q','Mapping table for relationship between "
10358
0
            "%q and %q',%s,0)",
10359
0
            osMappingTableName.c_str(), /*table_name*/
10360
0
            osMappingTableName.c_str(), /*identifier*/
10361
0
            osLeftTableName.c_str(),    /*description left table name*/
10362
0
            osRightTableName.c_str(),   /*description right table name*/
10363
0
            GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10364
10365
        // Note -- we explicitly ignore failures here, because hey, we aren't really
10366
        // supposed to be adding this table to gpkg_contents anyway!
10367
0
        (void)SQLCommand(hDB, pszSQL);
10368
0
        sqlite3_free(pszSQL);
10369
10370
0
        pszSQL = sqlite3_mprintf(
10371
0
            "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10372
0
            osMappingTableName.c_str(), osMappingTableName.c_str());
10373
0
        eErr = SQLCommand(hDB, pszSQL);
10374
0
        sqlite3_free(pszSQL);
10375
0
        if (eErr != OGRERR_NONE)
10376
0
        {
10377
0
            failureReason = ("Could not create index for " +
10378
0
                             osMappingTableName + " (base_id)")
10379
0
                                .c_str();
10380
0
            return false;
10381
0
        }
10382
10383
0
        pszSQL = sqlite3_mprintf(
10384
0
            "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10385
0
            osMappingTableName.c_str(), osMappingTableName.c_str());
10386
0
        eErr = SQLCommand(hDB, pszSQL);
10387
0
        sqlite3_free(pszSQL);
10388
0
        if (eErr != OGRERR_NONE)
10389
0
        {
10390
0
            failureReason = ("Could not create index for " +
10391
0
                             osMappingTableName + " (related_id)")
10392
0
                                .c_str();
10393
0
            return false;
10394
0
        }
10395
0
    }
10396
0
    else
10397
0
    {
10398
        // validate mapping table structure
10399
0
        if (OGRGeoPackageTableLayer *poLayer =
10400
0
                cpl::down_cast<OGRGeoPackageTableLayer *>(
10401
0
                    GetLayerByName(osMappingTableName)))
10402
0
        {
10403
0
            if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10404
0
            {
10405
0
                failureReason =
10406
0
                    ("Field base_id must exist in " + osMappingTableName)
10407
0
                        .c_str();
10408
0
                return false;
10409
0
            }
10410
0
            if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10411
0
            {
10412
0
                failureReason =
10413
0
                    ("Field related_id must exist in " + osMappingTableName)
10414
0
                        .c_str();
10415
0
                return false;
10416
0
            }
10417
0
        }
10418
0
        else
10419
0
        {
10420
0
            failureReason =
10421
0
                ("Could not retrieve table " + osMappingTableName).c_str();
10422
0
            return false;
10423
0
        }
10424
0
    }
10425
10426
0
    char *pszSQL = sqlite3_mprintf(
10427
0
        "INSERT INTO gpkg_extensions "
10428
0
        "(table_name,column_name,extension_name,definition,scope) "
10429
0
        "VALUES ('%q', NULL, 'gpkg_related_tables', "
10430
0
        "'http://www.geopackage.org/18-000.html', "
10431
0
        "'read-write')",
10432
0
        osMappingTableName.c_str());
10433
0
    OGRErr eErr = SQLCommand(hDB, pszSQL);
10434
0
    sqlite3_free(pszSQL);
10435
0
    if (eErr != OGRERR_NONE)
10436
0
    {
10437
0
        failureReason = ("Could not insert mapping table " +
10438
0
                         osMappingTableName + " into gpkg_extensions")
10439
0
                            .c_str();
10440
0
        return false;
10441
0
    }
10442
10443
0
    pszSQL = sqlite3_mprintf(
10444
0
        "INSERT INTO gpkgext_relations "
10445
0
        "(base_table_name,base_primary_column,related_table_name,related_"
10446
0
        "primary_column,relation_name,mapping_table_name) "
10447
0
        "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10448
0
        osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10449
0
        osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10450
0
        osRelatedTableType.c_str(), osMappingTableName.c_str());
10451
0
    eErr = SQLCommand(hDB, pszSQL);
10452
0
    sqlite3_free(pszSQL);
10453
0
    if (eErr != OGRERR_NONE)
10454
0
    {
10455
0
        failureReason = "Could not insert relationship into gpkgext_relations";
10456
0
        return false;
10457
0
    }
10458
10459
0
    ClearCachedRelationships();
10460
0
    LoadRelationships();
10461
0
    return true;
10462
0
}
10463
10464
/************************************************************************/
10465
/*                         DeleteRelationship()                         */
10466
/************************************************************************/
10467
10468
bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10469
                                               std::string &failureReason)
10470
0
{
10471
0
    if (eAccess != GA_Update)
10472
0
    {
10473
0
        CPLError(CE_Failure, CPLE_NotSupported,
10474
0
                 "DeleteRelationship() not supported on read-only dataset");
10475
0
        return false;
10476
0
    }
10477
10478
    // ensure relationships are up to date before we try to remove one
10479
0
    ClearCachedRelationships();
10480
0
    LoadRelationships();
10481
10482
0
    std::string osMappingTableName;
10483
0
    {
10484
0
        const GDALRelationship *poRelationship = GetRelationship(name);
10485
0
        if (poRelationship == nullptr)
10486
0
        {
10487
0
            failureReason = "Could not find relationship with name " + name;
10488
0
            return false;
10489
0
        }
10490
10491
0
        osMappingTableName = poRelationship->GetMappingTableName();
10492
0
    }
10493
10494
    // DeleteLayerCommon will delete existing relationship objects, so we can't
10495
    // refer to poRelationship or any of its members previously obtained here
10496
0
    if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10497
0
    {
10498
0
        failureReason =
10499
0
            "Could not remove mapping layer name " + osMappingTableName;
10500
10501
        // relationships may have been left in an inconsistent state -- reload
10502
        // them now
10503
0
        ClearCachedRelationships();
10504
0
        LoadRelationships();
10505
0
        return false;
10506
0
    }
10507
10508
0
    ClearCachedRelationships();
10509
0
    LoadRelationships();
10510
0
    return true;
10511
0
}
10512
10513
/************************************************************************/
10514
/*                        UpdateRelationship()                          */
10515
/************************************************************************/
10516
10517
bool GDALGeoPackageDataset::UpdateRelationship(
10518
    std::unique_ptr<GDALRelationship> &&relationship,
10519
    std::string &failureReason)
10520
0
{
10521
0
    if (eAccess != GA_Update)
10522
0
    {
10523
0
        CPLError(CE_Failure, CPLE_NotSupported,
10524
0
                 "UpdateRelationship() not supported on read-only dataset");
10525
0
        return false;
10526
0
    }
10527
10528
    // ensure relationships are up to date before we try to update one
10529
0
    ClearCachedRelationships();
10530
0
    LoadRelationships();
10531
10532
0
    const std::string &osRelationshipName = relationship->GetName();
10533
0
    const std::string &osLeftTableName = relationship->GetLeftTableName();
10534
0
    const std::string &osRightTableName = relationship->GetRightTableName();
10535
0
    const std::string &osMappingTableName = relationship->GetMappingTableName();
10536
0
    const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10537
0
    const auto &aosRightTableFields = relationship->GetRightTableFields();
10538
10539
    // sanity checks
10540
0
    {
10541
0
        const GDALRelationship *poExistingRelationship =
10542
0
            GetRelationship(osRelationshipName);
10543
0
        if (poExistingRelationship == nullptr)
10544
0
        {
10545
0
            failureReason =
10546
0
                "The relationship should already exist to be updated";
10547
0
            return false;
10548
0
        }
10549
10550
0
        if (!ValidateRelationship(relationship.get(), failureReason))
10551
0
        {
10552
0
            return false;
10553
0
        }
10554
10555
        // we don't permit changes to the participating tables
10556
0
        if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10557
0
        {
10558
0
            failureReason = ("Cannot change base table from " +
10559
0
                             poExistingRelationship->GetLeftTableName() +
10560
0
                             " to " + osLeftTableName)
10561
0
                                .c_str();
10562
0
            return false;
10563
0
        }
10564
0
        if (osRightTableName != poExistingRelationship->GetRightTableName())
10565
0
        {
10566
0
            failureReason = ("Cannot change related table from " +
10567
0
                             poExistingRelationship->GetRightTableName() +
10568
0
                             " to " + osRightTableName)
10569
0
                                .c_str();
10570
0
            return false;
10571
0
        }
10572
0
        if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10573
0
        {
10574
0
            failureReason = ("Cannot change mapping table from " +
10575
0
                             poExistingRelationship->GetMappingTableName() +
10576
0
                             " to " + osMappingTableName)
10577
0
                                .c_str();
10578
0
            return false;
10579
0
        }
10580
0
    }
10581
10582
0
    std::string osRelatedTableType = relationship->GetRelatedTableType();
10583
0
    if (osRelatedTableType.empty())
10584
0
    {
10585
0
        osRelatedTableType = "features";
10586
0
    }
10587
10588
0
    char *pszSQL = sqlite3_mprintf(
10589
0
        "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10590
0
        osMappingTableName.c_str());
10591
0
    OGRErr eErr = SQLCommand(hDB, pszSQL);
10592
0
    sqlite3_free(pszSQL);
10593
0
    if (eErr != OGRERR_NONE)
10594
0
    {
10595
0
        failureReason =
10596
0
            "Could not delete old relationship from gpkgext_relations";
10597
0
        return false;
10598
0
    }
10599
10600
0
    pszSQL = sqlite3_mprintf(
10601
0
        "INSERT INTO gpkgext_relations "
10602
0
        "(base_table_name,base_primary_column,related_table_name,related_"
10603
0
        "primary_column,relation_name,mapping_table_name) "
10604
0
        "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10605
0
        osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10606
0
        osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10607
0
        osRelatedTableType.c_str(), osMappingTableName.c_str());
10608
0
    eErr = SQLCommand(hDB, pszSQL);
10609
0
    sqlite3_free(pszSQL);
10610
0
    if (eErr != OGRERR_NONE)
10611
0
    {
10612
0
        failureReason =
10613
0
            "Could not insert updated relationship into gpkgext_relations";
10614
0
        return false;
10615
0
    }
10616
10617
0
    ClearCachedRelationships();
10618
0
    LoadRelationships();
10619
0
    return true;
10620
0
}
10621
10622
/************************************************************************/
10623
/*                    GetSqliteMasterContent()                          */
10624
/************************************************************************/
10625
10626
const std::vector<SQLSqliteMasterContent> &
10627
GDALGeoPackageDataset::GetSqliteMasterContent()
10628
34
{
10629
34
    if (m_aoSqliteMasterContent.empty())
10630
34
    {
10631
34
        auto oResultTable =
10632
34
            SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10633
34
        if (oResultTable)
10634
34
        {
10635
755
            for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10636
721
            {
10637
721
                SQLSqliteMasterContent row;
10638
721
                const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10639
721
                row.osSQL = pszSQL ? pszSQL : "";
10640
721
                const char *pszType = oResultTable->GetValue(1, rowCnt);
10641
721
                row.osType = pszType ? pszType : "";
10642
721
                const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10643
721
                row.osTableName = pszTableName ? pszTableName : "";
10644
721
                m_aoSqliteMasterContent.emplace_back(std::move(row));
10645
721
            }
10646
34
        }
10647
34
    }
10648
34
    return m_aoSqliteMasterContent;
10649
34
}