Coverage Report

Created: 2025-12-03 08:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp
Line
Count
Source
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
490
{
192
490
    CPLAssert(hDB != nullptr);
193
194
490
    const CPLString osPragma(CPLString().Printf("PRAGMA application_id = %u;"
195
490
                                                "PRAGMA user_version = %u",
196
490
                                                m_nApplicationId,
197
490
                                                m_nUserVersion));
198
490
    return SQLCommand(hDB, osPragma.c_str());
199
490
}
200
201
bool GDALGeoPackageDataset::CloseDB()
202
2.59k
{
203
2.59k
    OGRSQLiteUnregisterSQLFunctions(m_pSQLFunctionData);
204
2.59k
    m_pSQLFunctionData = nullptr;
205
2.59k
    return OGRSQLiteBaseDataSource::CloseDB();
206
2.59k
}
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
294
{
224
294
    CPLPushErrorHandler(CPLQuietErrorHandler);
225
294
    const OGRErr eErr = poSpatialRef->importFromEPSG(nEPSGCode);
226
294
    CPLPopErrorHandler();
227
294
    CPLErrorReset();
228
294
    return eErr;
229
294
}
230
231
std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
232
GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG,
233
                                     bool bEmitErrorIfNotFound)
234
421
{
235
421
    const auto oIter = m_oMapSrsIdToSrs.find(iSrsId);
236
421
    if (oIter != m_oMapSrsIdToSrs.end())
237
147
    {
238
147
        if (oIter->second == nullptr)
239
15
            return nullptr;
240
132
        oIter->second->Reference();
241
132
        return std::unique_ptr<OGRSpatialReference,
242
132
                               OGRSpatialReferenceReleaser>(oIter->second);
243
147
    }
244
245
274
    if (iSrsId == 0 || iSrsId == -1)
246
12
    {
247
12
        OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
248
12
        poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
249
250
        // See corresponding tests in GDALGeoPackageDataset::GetSrsId
251
12
        if (iSrsId == 0)
252
12
        {
253
12
            poSpatialRef->SetGeogCS("Undefined geographic SRS", "unknown",
254
12
                                    "unknown", SRS_WGS84_SEMIMAJOR,
255
12
                                    SRS_WGS84_INVFLATTENING);
256
12
        }
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
12
        m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
264
12
        poSpatialRef->Reference();
265
12
        return std::unique_ptr<OGRSpatialReference,
266
12
                               OGRSpatialReferenceReleaser>(poSpatialRef);
267
12
    }
268
269
262
    CPLString oSQL;
270
262
    oSQL.Printf("SELECT srs_name, definition, organization, "
271
262
                "organization_coordsys_id%s%s "
272
262
                "FROM gpkg_spatial_ref_sys WHERE "
273
262
                "srs_id = %d LIMIT 2",
274
262
                m_bHasDefinition12_063 ? ", definition_12_063" : "",
275
262
                m_bHasEpochColumn ? ", epoch" : "", iSrsId);
276
277
262
    auto oResult = SQLQuery(hDB, oSQL.c_str());
278
279
262
    if (!oResult || oResult->RowCount() != 1)
280
139
    {
281
139
        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
139
        else if (bEmitErrorIfNotFound)
296
139
        {
297
139
            CPLError(CE_Warning, CPLE_AppDefined,
298
139
                     "unable to read srs_id '%d' from gpkg_spatial_ref_sys",
299
139
                     iSrsId);
300
139
            m_oMapSrsIdToSrs[iSrsId] = nullptr;
301
139
        }
302
139
        return nullptr;
303
139
    }
304
305
123
    const char *pszName = oResult->GetValue(0, 0);
306
123
    if (pszName && EQUAL(pszName, "Undefined SRS"))
307
1
    {
308
1
        m_oMapSrsIdToSrs[iSrsId] = nullptr;
309
1
        return nullptr;
310
1
    }
311
122
    const char *pszWkt = oResult->GetValue(1, 0);
312
122
    if (pszWkt == nullptr)
313
0
        return nullptr;
314
122
    const char *pszOrganization = oResult->GetValue(2, 0);
315
122
    const char *pszOrganizationCoordsysID = oResult->GetValue(3, 0);
316
122
    const char *pszWkt2 =
317
122
        m_bHasDefinition12_063 ? oResult->GetValue(4, 0) : nullptr;
318
122
    if (pszWkt2 && !EQUAL(pszWkt2, "undefined"))
319
1
        pszWkt = pszWkt2;
320
122
    const char *pszCoordinateEpoch =
321
122
        m_bHasEpochColumn ? oResult->GetValue(5, 0) : nullptr;
322
122
    const double dfCoordinateEpoch =
323
122
        pszCoordinateEpoch ? CPLAtof(pszCoordinateEpoch) : 0.0;
324
325
122
    OGRSpatialReference *poSpatialRef = new OGRSpatialReference();
326
122
    poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
327
    // Try to import first from EPSG code, and then from WKT
328
122
    if (!(pszOrganization && pszOrganizationCoordsysID &&
329
122
          EQUAL(pszOrganization, "EPSG") &&
330
111
          (atoi(pszOrganizationCoordsysID) == iSrsId ||
331
0
           (dfCoordinateEpoch > 0 && strstr(pszWkt, "DYNAMIC[") == nullptr)) &&
332
111
          GDALGPKGImportFromEPSG(
333
111
              poSpatialRef, atoi(pszOrganizationCoordsysID)) == OGRERR_NONE) &&
334
11
        poSpatialRef->importFromWkt(pszWkt) != OGRERR_NONE)
335
8
    {
336
8
        CPLError(CE_Warning, CPLE_AppDefined,
337
8
                 "Unable to parse srs_id '%d' well-known text '%s'", iSrsId,
338
8
                 pszWkt);
339
8
        delete poSpatialRef;
340
8
        m_oMapSrsIdToSrs[iSrsId] = nullptr;
341
8
        return nullptr;
342
8
    }
343
344
114
    poSpatialRef->StripTOWGS84IfKnownDatumAndAllowed();
345
114
    poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch);
346
114
    m_oMapSrsIdToSrs[iSrsId] = poSpatialRef;
347
114
    poSpatialRef->Reference();
348
114
    return std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
349
114
        poSpatialRef);
350
122
}
351
352
const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS)
353
183
{
354
183
    const char *pszName = oSRS.GetName();
355
183
    if (pszName)
356
183
        return pszName;
357
358
    // Something odd.  Return empty.
359
0
    return "Unnamed SRS";
360
183
}
361
362
/* Add the definition_12_063 column to an existing gpkg_spatial_ref_sys table */
363
bool GDALGeoPackageDataset::ConvertGpkgSpatialRefSysToExtensionWkt2(
364
    bool bForceEpoch)
365
21
{
366
21
    const bool bAddEpoch = (m_nUserVersion >= GPKG_1_4_VERSION || bForceEpoch);
367
21
    auto oResultTable = SQLQuery(
368
21
        hDB, "SELECT srs_name, srs_id, organization, organization_coordsys_id, "
369
21
             "definition, description FROM gpkg_spatial_ref_sys LIMIT 100000");
370
21
    if (!oResultTable)
371
0
        return false;
372
373
    // Temporary remove foreign key checks
374
21
    const GPKGTemporaryForeignKeyCheckDisabler
375
21
        oGPKGTemporaryForeignKeyCheckDisabler(this);
376
377
21
    bool bRet = SoftStartTransaction() == OGRERR_NONE;
378
379
21
    if (bRet)
380
21
    {
381
21
        std::string osSQL("CREATE TABLE gpkg_spatial_ref_sys_temp ("
382
21
                          "srs_name TEXT NOT NULL,"
383
21
                          "srs_id INTEGER NOT NULL PRIMARY KEY,"
384
21
                          "organization TEXT NOT NULL,"
385
21
                          "organization_coordsys_id INTEGER NOT NULL,"
386
21
                          "definition TEXT NOT NULL,"
387
21
                          "description TEXT, "
388
21
                          "definition_12_063 TEXT NOT NULL");
389
21
        if (bAddEpoch)
390
21
            osSQL += ", epoch DOUBLE";
391
21
        osSQL += ")";
392
21
        bRet = SQLCommand(hDB, osSQL.c_str()) == OGRERR_NONE;
393
21
    }
394
395
21
    if (bRet)
396
21
    {
397
96
        for (int i = 0; bRet && i < oResultTable->RowCount(); i++)
398
75
        {
399
75
            const char *pszSrsName = oResultTable->GetValue(0, i);
400
75
            const char *pszSrsId = oResultTable->GetValue(1, i);
401
75
            const char *pszOrganization = oResultTable->GetValue(2, i);
402
75
            const char *pszOrganizationCoordsysID =
403
75
                oResultTable->GetValue(3, i);
404
75
            const char *pszDefinition = oResultTable->GetValue(4, i);
405
75
            if (pszSrsName == nullptr || pszSrsId == nullptr ||
406
75
                pszOrganization == nullptr ||
407
75
                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
75
            const char *pszDescription = oResultTable->GetValue(5, i);
414
75
            char *pszSQL;
415
416
75
            OGRSpatialReference oSRS;
417
75
            if (pszOrganization && pszOrganizationCoordsysID &&
418
75
                EQUAL(pszOrganization, "EPSG"))
419
25
            {
420
25
                oSRS.importFromEPSG(atoi(pszOrganizationCoordsysID));
421
25
            }
422
75
            if (!oSRS.IsEmpty() && pszDefinition &&
423
25
                !EQUAL(pszDefinition, "undefined"))
424
25
            {
425
25
                oSRS.SetFromUserInput(pszDefinition);
426
25
            }
427
75
            char *pszWKT2 = nullptr;
428
75
            if (!oSRS.IsEmpty())
429
25
            {
430
25
                const char *const apszOptionsWkt2[] = {"FORMAT=WKT2_2015",
431
25
                                                       nullptr};
432
25
                oSRS.exportToWkt(&pszWKT2, apszOptionsWkt2);
433
25
                if (pszWKT2 && pszWKT2[0] == '\0')
434
0
                {
435
0
                    CPLFree(pszWKT2);
436
0
                    pszWKT2 = nullptr;
437
0
                }
438
25
            }
439
75
            if (pszWKT2 == nullptr)
440
50
            {
441
50
                pszWKT2 = CPLStrdup("undefined");
442
50
            }
443
444
75
            if (pszDescription)
445
71
            {
446
71
                pszSQL = sqlite3_mprintf(
447
71
                    "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
448
71
                    "organization, organization_coordsys_id, definition, "
449
71
                    "description, definition_12_063) VALUES ('%q', '%q', '%q', "
450
71
                    "'%q', '%q', '%q', '%q')",
451
71
                    pszSrsName, pszSrsId, pszOrganization,
452
71
                    pszOrganizationCoordsysID, pszDefinition, pszDescription,
453
71
                    pszWKT2);
454
71
            }
455
4
            else
456
4
            {
457
4
                pszSQL = sqlite3_mprintf(
458
4
                    "INSERT INTO gpkg_spatial_ref_sys_temp(srs_name, srs_id, "
459
4
                    "organization, organization_coordsys_id, definition, "
460
4
                    "description, definition_12_063) VALUES ('%q', '%q', '%q', "
461
4
                    "'%q', '%q', NULL, '%q')",
462
4
                    pszSrsName, pszSrsId, pszOrganization,
463
4
                    pszOrganizationCoordsysID, pszDefinition, pszWKT2);
464
4
            }
465
466
75
            CPLFree(pszWKT2);
467
75
            bRet &= SQLCommand(hDB, pszSQL) == OGRERR_NONE;
468
75
            sqlite3_free(pszSQL);
469
75
        }
470
21
    }
471
472
21
    if (bRet)
473
21
    {
474
21
        bRet =
475
21
            SQLCommand(hDB, "DROP TABLE gpkg_spatial_ref_sys") == OGRERR_NONE;
476
21
    }
477
21
    if (bRet)
478
21
    {
479
21
        bRet = SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys_temp RENAME "
480
21
                               "TO gpkg_spatial_ref_sys") == OGRERR_NONE;
481
21
    }
482
21
    if (bRet)
483
21
    {
484
21
        bRet = OGRERR_NONE == CreateExtensionsTableIfNecessary() &&
485
21
               OGRERR_NONE == SQLCommand(hDB,
486
21
                                         "INSERT INTO gpkg_extensions "
487
21
                                         "(table_name, column_name, "
488
21
                                         "extension_name, definition, scope) "
489
21
                                         "VALUES "
490
21
                                         "('gpkg_spatial_ref_sys', "
491
21
                                         "'definition_12_063', 'gpkg_crs_wkt', "
492
21
                                         "'http://www.geopackage.org/spec120/"
493
21
                                         "#extension_crs_wkt', 'read-write')");
494
21
    }
495
21
    if (bRet && bAddEpoch)
496
21
    {
497
21
        bRet =
498
21
            OGRERR_NONE ==
499
21
                SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
500
21
                                "'gpkg_crs_wkt_1_1' "
501
21
                                "WHERE extension_name = 'gpkg_crs_wkt'") &&
502
21
            OGRERR_NONE ==
503
21
                SQLCommand(
504
21
                    hDB,
505
21
                    "INSERT INTO gpkg_extensions "
506
21
                    "(table_name, column_name, extension_name, definition, "
507
21
                    "scope) "
508
21
                    "VALUES "
509
21
                    "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
510
21
                    "'http://www.geopackage.org/spec/#extension_crs_wkt', "
511
21
                    "'read-write')");
512
21
    }
513
21
    if (bRet)
514
21
    {
515
21
        SoftCommitTransaction();
516
21
        m_bHasDefinition12_063 = true;
517
21
        if (bAddEpoch)
518
21
            m_bHasEpochColumn = true;
519
21
    }
520
0
    else
521
0
    {
522
0
        SoftRollbackTransaction();
523
0
    }
524
525
21
    return bRet;
526
21
}
527
528
int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn)
529
1.19k
{
530
1.19k
    const char *pszName = poSRSIn ? poSRSIn->GetName() : nullptr;
531
1.19k
    if (!poSRSIn || poSRSIn->IsEmpty() ||
532
395
        (pszName && EQUAL(pszName, "Undefined SRS")))
533
801
    {
534
801
        OGRErr err = OGRERR_NONE;
535
801
        const int nSRSId = SQLGetInteger(
536
801
            hDB,
537
801
            "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE srs_name = "
538
801
            "'Undefined SRS' AND organization = 'GDAL'",
539
801
            &err);
540
801
        if (err == OGRERR_NONE)
541
566
            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
235
        const char *pszSQL;
547
235
#define UNDEFINED_CRS_SRS_ID 99999
548
235
        static_assert(UNDEFINED_CRS_SRS_ID == FIRST_CUSTOM_SRSID - 1);
549
235
#define STRINGIFY(x) #x
550
235
#define XSTRINGIFY(x) STRINGIFY(x)
551
235
        if (m_bHasDefinition12_063)
552
5
        {
553
            /* clang-format off */
554
5
            pszSQL =
555
5
                "INSERT INTO gpkg_spatial_ref_sys "
556
5
                "(srs_name,srs_id,organization,organization_coordsys_id,"
557
5
                "definition, definition_12_063, description) VALUES "
558
5
                "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
559
5
                XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
560
5
                "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
561
5
                "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
562
5
                "AXIS[\"Northing\",NORTH]]',"
563
5
                "'ENGCRS[\"Undefined SRS\",EDATUM[\"unknown\"],CS[Cartesian,2],"
564
5
                "AXIS[\"easting\",east,ORDER[1],LENGTHUNIT[\"unknown\",0]],"
565
5
                "AXIS[\"northing\",north,ORDER[2],LENGTHUNIT[\"unknown\",0]]]',"
566
5
                "'Custom undefined coordinate reference system')";
567
            /* clang-format on */
568
5
        }
569
230
        else
570
230
        {
571
            /* clang-format off */
572
230
            pszSQL =
573
230
                "INSERT INTO gpkg_spatial_ref_sys "
574
230
                "(srs_name,srs_id,organization,organization_coordsys_id,"
575
230
                "definition, description) VALUES "
576
230
                "('Undefined SRS'," XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ",'GDAL',"
577
230
                XSTRINGIFY(UNDEFINED_CRS_SRS_ID) ","
578
230
                "'LOCAL_CS[\"Undefined SRS\",LOCAL_DATUM[\"unknown\",32767],"
579
230
                "UNIT[\"unknown\",0],AXIS[\"Easting\",EAST],"
580
230
                "AXIS[\"Northing\",NORTH]]',"
581
230
                "'Custom undefined coordinate reference system')";
582
            /* clang-format on */
583
230
        }
584
235
        if (SQLCommand(hDB, pszSQL) == OGRERR_NONE)
585
235
            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
235
    }
591
592
395
    std::unique_ptr<OGRSpatialReference> poSRS(poSRSIn->Clone());
593
594
395
    if (poSRS->IsGeographic() || poSRS->IsLocal())
595
41
    {
596
        // See corresponding tests in GDALGeoPackageDataset::GetSpatialRef
597
41
        if (pszName != nullptr && strlen(pszName) > 0)
598
41
        {
599
41
            if (EQUAL(pszName, "Undefined geographic SRS"))
600
0
                return 0;
601
602
41
            if (EQUAL(pszName, "Undefined Cartesian SRS"))
603
0
                return -1;
604
41
        }
605
41
    }
606
607
395
    const char *pszAuthorityName = poSRS->GetAuthorityName(nullptr);
608
609
395
    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
395
    char *pszSQL = nullptr;
633
395
    int nSRSId = DEFAULT_SRID;
634
395
    int nAuthorityCode = 0;
635
395
    OGRErr err = OGRERR_NONE;
636
395
    bool bCanUseAuthorityCode = false;
637
395
    const char *const apszIsSameOptions[] = {
638
395
        "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES",
639
395
        "IGNORE_COORDINATE_EPOCH=YES", nullptr};
640
395
    if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0)
641
395
    {
642
395
        const char *pszAuthorityCode = poSRS->GetAuthorityCode(nullptr);
643
395
        if (pszAuthorityCode)
644
395
        {
645
395
            if (CPLGetValueType(pszAuthorityCode) == CPL_VALUE_INTEGER)
646
395
            {
647
395
                nAuthorityCode = atoi(pszAuthorityCode);
648
395
            }
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
395
        }
659
395
    }
660
661
395
    if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
662
395
        poSRSIn->GetCoordinateEpoch() == 0)
663
395
    {
664
395
        pszSQL =
665
395
            sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
666
395
                            "upper(organization) = upper('%q') AND "
667
395
                            "organization_coordsys_id = %d",
668
395
                            pszAuthorityName, nAuthorityCode);
669
670
395
        nSRSId = SQLGetInteger(hDB, pszSQL, &err);
671
395
        sqlite3_free(pszSQL);
672
673
        // Got a match? Return it!
674
395
        if (OGRERR_NONE == err)
675
212
        {
676
212
            auto poRefSRS = GetSpatialRef(nSRSId);
677
212
            bool bOK =
678
212
                (poRefSRS == nullptr ||
679
212
                 poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) ||
680
0
                 !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")));
681
212
            if (bOK)
682
212
            {
683
212
                return nSRSId;
684
212
            }
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
212
        }
697
395
    }
698
699
    // Translate SRS to WKT.
700
183
    CPLCharUniquePtr pszWKT1;
701
183
    CPLCharUniquePtr pszWKT2_2015;
702
183
    CPLCharUniquePtr pszWKT2_2019;
703
183
    const char *const apszOptionsWkt1[] = {"FORMAT=WKT1_GDAL", nullptr};
704
183
    const char *const apszOptionsWkt2_2015[] = {"FORMAT=WKT2_2015", nullptr};
705
183
    const char *const apszOptionsWkt2_2019[] = {"FORMAT=WKT2_2019", nullptr};
706
707
183
    std::string osEpochTest;
708
183
    if (poSRSIn->GetCoordinateEpoch() > 0 && m_bHasEpochColumn)
709
0
    {
710
0
        osEpochTest =
711
0
            CPLSPrintf(" AND epoch = %.17g", poSRSIn->GetCoordinateEpoch());
712
0
    }
713
714
183
    if (!(poSRS->IsGeographic() && poSRS->GetAxesCount() == 3) &&
715
162
        !poSRS->IsDerivedGeographic())
716
162
    {
717
162
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
718
162
        char *pszTmp = nullptr;
719
162
        poSRS->exportToWkt(&pszTmp, apszOptionsWkt1);
720
162
        pszWKT1.reset(pszTmp);
721
162
        if (pszWKT1 && pszWKT1.get()[0] == '\0')
722
0
        {
723
0
            pszWKT1.reset();
724
0
        }
725
162
    }
726
183
    {
727
183
        CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
728
183
        char *pszTmp = nullptr;
729
183
        poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2015);
730
183
        pszWKT2_2015.reset(pszTmp);
731
183
        if (pszWKT2_2015 && pszWKT2_2015.get()[0] == '\0')
732
0
        {
733
0
            pszWKT2_2015.reset();
734
0
        }
735
183
    }
736
183
    {
737
183
        char *pszTmp = nullptr;
738
183
        poSRS->exportToWkt(&pszTmp, apszOptionsWkt2_2019);
739
183
        pszWKT2_2019.reset(pszTmp);
740
183
        if (pszWKT2_2019 && pszWKT2_2019.get()[0] == '\0')
741
0
        {
742
0
            pszWKT2_2019.reset();
743
0
        }
744
183
    }
745
746
183
    if (!pszWKT1 && !pszWKT2_2015 && !pszWKT2_2019)
747
0
    {
748
0
        return DEFAULT_SRID;
749
0
    }
750
751
183
    if (poSRSIn->GetCoordinateEpoch() == 0 || m_bHasEpochColumn)
752
183
    {
753
        // Search if there is already an existing entry with this WKT
754
183
        if (m_bHasDefinition12_063 && (pszWKT2_2015 || pszWKT2_2019))
755
6
        {
756
6
            if (pszWKT1)
757
6
            {
758
6
                pszSQL = sqlite3_mprintf(
759
6
                    "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
760
6
                    "(definition = '%q' OR definition_12_063 IN ('%q','%q'))%s",
761
6
                    pszWKT1.get(),
762
6
                    pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
763
6
                    pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
764
6
                    osEpochTest.c_str());
765
6
            }
766
0
            else
767
0
            {
768
0
                pszSQL = sqlite3_mprintf(
769
0
                    "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
770
0
                    "definition_12_063 IN ('%q', '%q')%s",
771
0
                    pszWKT2_2015 ? pszWKT2_2015.get() : "invalid",
772
0
                    pszWKT2_2019 ? pszWKT2_2019.get() : "invalid",
773
0
                    osEpochTest.c_str());
774
0
            }
775
6
        }
776
177
        else if (pszWKT1)
777
156
        {
778
156
            pszSQL =
779
156
                sqlite3_mprintf("SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
780
156
                                "definition = '%q'%s",
781
156
                                pszWKT1.get(), osEpochTest.c_str());
782
156
        }
783
21
        else
784
21
        {
785
21
            pszSQL = nullptr;
786
21
        }
787
183
        if (pszSQL)
788
162
        {
789
162
            nSRSId = SQLGetInteger(hDB, pszSQL, &err);
790
162
            sqlite3_free(pszSQL);
791
162
            if (OGRERR_NONE == err)
792
0
            {
793
0
                return nSRSId;
794
0
            }
795
162
        }
796
183
    }
797
798
183
    if (pszAuthorityName != nullptr && strlen(pszAuthorityName) > 0 &&
799
183
        poSRSIn->GetCoordinateEpoch() == 0)
800
183
    {
801
183
        bool bTryToReuseSRSId = true;
802
183
        if (EQUAL(pszAuthorityName, "EPSG"))
803
183
        {
804
183
            OGRSpatialReference oSRS_EPSG;
805
183
            if (GDALGPKGImportFromEPSG(&oSRS_EPSG, nAuthorityCode) ==
806
183
                OGRERR_NONE)
807
183
            {
808
183
                if (!poSRS->IsSame(&oSRS_EPSG, apszIsSameOptions) &&
809
0
                    CPLTestBool(
810
0
                        CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES")))
811
0
                {
812
0
                    bTryToReuseSRSId = false;
813
0
                    CPLError(
814
0
                        CE_Warning, CPLE_AppDefined,
815
0
                        "Passed SRS uses %s:%d identification, but its "
816
0
                        "definition is not compatible with the "
817
0
                        "official definition of the object. "
818
0
                        "Registering it as a non-%s entry into the database.",
819
0
                        pszAuthorityName, nAuthorityCode, pszAuthorityName);
820
0
                    pszAuthorityName = nullptr;
821
0
                    nAuthorityCode = 0;
822
0
                }
823
183
            }
824
183
        }
825
183
        if (bTryToReuseSRSId)
826
183
        {
827
            // No match, but maybe we can use the nAuthorityCode as the nSRSId?
828
183
            pszSQL = sqlite3_mprintf(
829
183
                "SELECT Count(*) FROM gpkg_spatial_ref_sys WHERE "
830
183
                "srs_id = %d",
831
183
                nAuthorityCode);
832
833
            // Yep, we can!
834
183
            if (SQLGetInteger(hDB, pszSQL, nullptr) == 0)
835
183
                bCanUseAuthorityCode = true;
836
183
            sqlite3_free(pszSQL);
837
183
        }
838
183
    }
839
840
183
    bool bConvertGpkgSpatialRefSysToExtensionWkt2 = false;
841
183
    bool bForceEpoch = false;
842
183
    if (!m_bHasDefinition12_063 && pszWKT1 == nullptr &&
843
21
        (pszWKT2_2015 != nullptr || pszWKT2_2019 != nullptr))
844
21
    {
845
21
        bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
846
21
    }
847
848
    // Add epoch column if needed
849
183
    if (poSRSIn->GetCoordinateEpoch() > 0 && !m_bHasEpochColumn)
850
0
    {
851
0
        if (m_bHasDefinition12_063)
852
0
        {
853
0
            if (SoftStartTransaction() != OGRERR_NONE)
854
0
                return DEFAULT_SRID;
855
0
            if (SQLCommand(hDB, "ALTER TABLE gpkg_spatial_ref_sys "
856
0
                                "ADD COLUMN epoch DOUBLE") != OGRERR_NONE ||
857
0
                SQLCommand(hDB, "UPDATE gpkg_extensions SET extension_name = "
858
0
                                "'gpkg_crs_wkt_1_1' "
859
0
                                "WHERE extension_name = 'gpkg_crs_wkt'") !=
860
0
                    OGRERR_NONE ||
861
0
                SQLCommand(
862
0
                    hDB,
863
0
                    "INSERT INTO gpkg_extensions "
864
0
                    "(table_name, column_name, extension_name, definition, "
865
0
                    "scope) "
866
0
                    "VALUES "
867
0
                    "('gpkg_spatial_ref_sys', 'epoch', 'gpkg_crs_wkt_1_1', "
868
0
                    "'http://www.geopackage.org/spec/#extension_crs_wkt', "
869
0
                    "'read-write')") != OGRERR_NONE)
870
0
            {
871
0
                SoftRollbackTransaction();
872
0
                return DEFAULT_SRID;
873
0
            }
874
875
0
            if (SoftCommitTransaction() != OGRERR_NONE)
876
0
                return DEFAULT_SRID;
877
878
0
            m_bHasEpochColumn = true;
879
0
        }
880
0
        else
881
0
        {
882
0
            bConvertGpkgSpatialRefSysToExtensionWkt2 = true;
883
0
            bForceEpoch = true;
884
0
        }
885
0
    }
886
887
183
    if (bConvertGpkgSpatialRefSysToExtensionWkt2 &&
888
21
        !ConvertGpkgSpatialRefSysToExtensionWkt2(bForceEpoch))
889
0
    {
890
0
        return DEFAULT_SRID;
891
0
    }
892
893
    // Reuse the authority code number as SRS_ID if we can
894
183
    if (bCanUseAuthorityCode)
895
183
    {
896
183
        nSRSId = nAuthorityCode;
897
183
    }
898
    // Otherwise, generate a new SRS_ID number (max + 1)
899
0
    else
900
0
    {
901
        // Get the current maximum srid in the srs table.
902
0
        const int nMaxSRSId = SQLGetInteger(
903
0
            hDB, "SELECT MAX(srs_id) FROM gpkg_spatial_ref_sys", nullptr);
904
0
        nSRSId = std::max(FIRST_CUSTOM_SRSID, nMaxSRSId + 1);
905
0
    }
906
907
183
    std::string osEpochColumn;
908
183
    std::string osEpochVal;
909
183
    if (poSRSIn->GetCoordinateEpoch() > 0)
910
0
    {
911
0
        osEpochColumn = ", epoch";
912
0
        osEpochVal = CPLSPrintf(", %.17g", poSRSIn->GetCoordinateEpoch());
913
0
    }
914
915
    // Add new SRS row to gpkg_spatial_ref_sys.
916
183
    if (m_bHasDefinition12_063)
917
27
    {
918
        // Force WKT2_2019 when we have a dynamic CRS and coordinate epoch
919
27
        const char *pszWKT2 = poSRSIn->IsDynamic() &&
920
0
                                      poSRSIn->GetCoordinateEpoch() > 0 &&
921
0
                                      pszWKT2_2019
922
27
                                  ? pszWKT2_2019.get()
923
27
                              : pszWKT2_2015 ? pszWKT2_2015.get()
924
27
                                             : pszWKT2_2019.get();
925
926
27
        if (pszAuthorityName != nullptr && nAuthorityCode > 0)
927
27
        {
928
27
            pszSQL = sqlite3_mprintf(
929
27
                "INSERT INTO gpkg_spatial_ref_sys "
930
27
                "(srs_name,srs_id,organization,organization_coordsys_id,"
931
27
                "definition, definition_12_063%s) VALUES "
932
27
                "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
933
27
                osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId,
934
27
                pszAuthorityName, nAuthorityCode,
935
27
                pszWKT1 ? pszWKT1.get() : "undefined",
936
27
                pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
937
27
        }
938
0
        else
939
0
        {
940
0
            pszSQL = sqlite3_mprintf(
941
0
                "INSERT INTO gpkg_spatial_ref_sys "
942
0
                "(srs_name,srs_id,organization,organization_coordsys_id,"
943
0
                "definition, definition_12_063%s) VALUES "
944
0
                "('%q', %d, upper('%q'), %d, '%q', '%q'%s)",
945
0
                osEpochColumn.c_str(), GetSrsName(*poSRS), nSRSId, "NONE",
946
0
                nSRSId, pszWKT1 ? pszWKT1.get() : "undefined",
947
0
                pszWKT2 ? pszWKT2 : "undefined", osEpochVal.c_str());
948
0
        }
949
27
    }
950
156
    else
951
156
    {
952
156
        if (pszAuthorityName != nullptr && nAuthorityCode > 0)
953
156
        {
954
156
            pszSQL = sqlite3_mprintf(
955
156
                "INSERT INTO gpkg_spatial_ref_sys "
956
156
                "(srs_name,srs_id,organization,organization_coordsys_id,"
957
156
                "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
958
156
                GetSrsName(*poSRS), nSRSId, pszAuthorityName, nAuthorityCode,
959
156
                pszWKT1 ? pszWKT1.get() : "undefined");
960
156
        }
961
0
        else
962
0
        {
963
0
            pszSQL = sqlite3_mprintf(
964
0
                "INSERT INTO gpkg_spatial_ref_sys "
965
0
                "(srs_name,srs_id,organization,organization_coordsys_id,"
966
0
                "definition) VALUES ('%q', %d, upper('%q'), %d, '%q')",
967
0
                GetSrsName(*poSRS), nSRSId, "NONE", nSRSId,
968
0
                pszWKT1 ? pszWKT1.get() : "undefined");
969
0
        }
970
156
    }
971
972
    // Add new row to gpkg_spatial_ref_sys.
973
183
    CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
974
975
    // Free everything that was allocated.
976
183
    sqlite3_free(pszSQL);
977
978
183
    return nSRSId;
979
183
}
980
981
/************************************************************************/
982
/*                       ~GDALGeoPackageDataset()                       */
983
/************************************************************************/
984
985
GDALGeoPackageDataset::~GDALGeoPackageDataset()
986
2.59k
{
987
2.59k
    GDALGeoPackageDataset::Close();
988
2.59k
}
989
990
/************************************************************************/
991
/*                              Close()                                 */
992
/************************************************************************/
993
994
CPLErr GDALGeoPackageDataset::Close()
995
3.60k
{
996
3.60k
    CPLErr eErr = CE_None;
997
3.60k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
998
2.59k
    {
999
2.59k
        if (eAccess == GA_Update && m_poParentDS == nullptr &&
1000
494
            !m_osRasterTable.empty() && !m_bGeoTransformValid)
1001
0
        {
1002
0
            CPLError(CE_Failure, CPLE_AppDefined,
1003
0
                     "Raster table %s not correctly initialized due to missing "
1004
0
                     "call to SetGeoTransform()",
1005
0
                     m_osRasterTable.c_str());
1006
0
        }
1007
1008
2.59k
        if (!IsMarkedSuppressOnClose() &&
1009
2.59k
            GDALGeoPackageDataset::FlushCache(true) != CE_None)
1010
0
        {
1011
0
            eErr = CE_Failure;
1012
0
        }
1013
1014
        // Destroy bands now since we don't want
1015
        // GDALGPKGMBTilesLikeRasterBand::FlushCache() to run after dataset
1016
        // destruction
1017
2.66k
        for (int i = 0; i < nBands; i++)
1018
72
            delete papoBands[i];
1019
2.59k
        nBands = 0;
1020
2.59k
        CPLFree(papoBands);
1021
2.59k
        papoBands = nullptr;
1022
1023
        // Destroy overviews before cleaning m_hTempDB as they could still
1024
        // need it
1025
2.59k
        m_apoOverviewDS.clear();
1026
1027
2.59k
        if (m_poParentDS)
1028
1
        {
1029
1
            hDB = nullptr;
1030
1
        }
1031
1032
2.59k
        m_apoLayers.clear();
1033
1034
2.59k
        std::map<int, OGRSpatialReference *>::iterator oIter =
1035
2.59k
            m_oMapSrsIdToSrs.begin();
1036
2.86k
        for (; oIter != m_oMapSrsIdToSrs.end(); ++oIter)
1037
274
        {
1038
274
            OGRSpatialReference *poSRS = oIter->second;
1039
274
            if (poSRS)
1040
126
                poSRS->Release();
1041
274
        }
1042
1043
2.59k
        if (!CloseDB())
1044
0
            eErr = CE_Failure;
1045
1046
2.59k
        if (OGRSQLiteBaseDataSource::Close() != CE_None)
1047
0
            eErr = CE_Failure;
1048
2.59k
    }
1049
3.60k
    return eErr;
1050
3.60k
}
1051
1052
/************************************************************************/
1053
/*                         ICanIWriteBlock()                            */
1054
/************************************************************************/
1055
1056
bool GDALGeoPackageDataset::ICanIWriteBlock()
1057
0
{
1058
0
    if (!GetUpdate())
1059
0
    {
1060
0
        CPLError(
1061
0
            CE_Failure, CPLE_NotSupported,
1062
0
            "IWriteBlock() not supported on dataset opened in read-only mode");
1063
0
        return false;
1064
0
    }
1065
1066
0
    if (m_pabyCachedTiles == nullptr)
1067
0
    {
1068
0
        return false;
1069
0
    }
1070
1071
0
    if (!m_bGeoTransformValid || m_nSRID == UNKNOWN_SRID)
1072
0
    {
1073
0
        CPLError(CE_Failure, CPLE_NotSupported,
1074
0
                 "IWriteBlock() not supported if georeferencing not set");
1075
0
        return false;
1076
0
    }
1077
0
    return true;
1078
0
}
1079
1080
/************************************************************************/
1081
/*                            IRasterIO()                               */
1082
/************************************************************************/
1083
1084
CPLErr GDALGeoPackageDataset::IRasterIO(
1085
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
1086
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
1087
    int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1088
    GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
1089
1090
0
{
1091
0
    CPLErr eErr = OGRSQLiteBaseDataSource::IRasterIO(
1092
0
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
1093
0
        eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1094
0
        psExtraArg);
1095
1096
    // If writing all bands, in non-shifted mode, flush all entirely written
1097
    // tiles This can avoid "stressing" the block cache with too many dirty
1098
    // blocks. Note: this logic would be useless with a per-dataset block cache.
1099
0
    if (eErr == CE_None && eRWFlag == GF_Write && nXSize == nBufXSize &&
1100
0
        nYSize == nBufYSize && nBandCount == nBands &&
1101
0
        m_nShiftXPixelsMod == 0 && m_nShiftYPixelsMod == 0)
1102
0
    {
1103
0
        auto poBand =
1104
0
            cpl::down_cast<GDALGPKGMBTilesLikeRasterBand *>(GetRasterBand(1));
1105
0
        int nBlockXSize, nBlockYSize;
1106
0
        poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1107
0
        const int nBlockXStart = DIV_ROUND_UP(nXOff, nBlockXSize);
1108
0
        const int nBlockYStart = DIV_ROUND_UP(nYOff, nBlockYSize);
1109
0
        const int nBlockXEnd = (nXOff + nXSize) / nBlockXSize;
1110
0
        const int nBlockYEnd = (nYOff + nYSize) / nBlockYSize;
1111
0
        for (int nBlockY = nBlockXStart; nBlockY < nBlockYEnd; nBlockY++)
1112
0
        {
1113
0
            for (int nBlockX = nBlockYStart; nBlockX < nBlockXEnd; nBlockX++)
1114
0
            {
1115
0
                GDALRasterBlock *poBlock =
1116
0
                    poBand->AccessibleTryGetLockedBlockRef(nBlockX, nBlockY);
1117
0
                if (poBlock)
1118
0
                {
1119
                    // GetDirty() should be true in most situation (otherwise
1120
                    // it means the block cache is under extreme pressure!)
1121
0
                    if (poBlock->GetDirty())
1122
0
                    {
1123
                        // IWriteBlock() on one band will check the dirty state
1124
                        // of the corresponding blocks in other bands, to decide
1125
                        // if it can call WriteTile(), so we have only to do
1126
                        // that on one of the bands
1127
0
                        if (poBlock->Write() != CE_None)
1128
0
                            eErr = CE_Failure;
1129
0
                    }
1130
0
                    poBlock->DropLock();
1131
0
                }
1132
0
            }
1133
0
        }
1134
0
    }
1135
1136
0
    return eErr;
1137
0
}
1138
1139
/************************************************************************/
1140
/*                          GetOGRTableLimit()                          */
1141
/************************************************************************/
1142
1143
static int GetOGRTableLimit()
1144
1.52k
{
1145
1.52k
    return atoi(CPLGetConfigOption("OGR_TABLE_LIMIT", "10000"));
1146
1.52k
}
1147
1148
/************************************************************************/
1149
/*                      GetNameTypeMapFromSQliteMaster()                */
1150
/************************************************************************/
1151
1152
const std::map<CPLString, CPLString> &
1153
GDALGeoPackageDataset::GetNameTypeMapFromSQliteMaster()
1154
1.62k
{
1155
1.62k
    if (!m_oMapNameToType.empty())
1156
1.13k
        return m_oMapNameToType;
1157
1158
496
    CPLString osSQL(
1159
496
        "SELECT name, type FROM sqlite_master WHERE "
1160
496
        "type IN ('view', 'table') OR "
1161
496
        "(name LIKE 'trigger_%_feature_count_%' AND type = 'trigger')");
1162
496
    const int nTableLimit = GetOGRTableLimit();
1163
496
    if (nTableLimit > 0)
1164
496
    {
1165
496
        osSQL += " LIMIT ";
1166
496
        osSQL += CPLSPrintf("%d", 1 + 3 * nTableLimit);
1167
496
    }
1168
1169
496
    auto oResult = SQLQuery(hDB, osSQL);
1170
496
    if (oResult)
1171
496
    {
1172
9.21k
        for (int i = 0; i < oResult->RowCount(); i++)
1173
8.71k
        {
1174
8.71k
            const char *pszName = oResult->GetValue(0, i);
1175
8.71k
            const char *pszType = oResult->GetValue(1, i);
1176
8.71k
            m_oMapNameToType[CPLString(pszName).toupper()] = pszType;
1177
8.71k
        }
1178
496
    }
1179
1180
496
    return m_oMapNameToType;
1181
1.62k
}
1182
1183
/************************************************************************/
1184
/*                    RemoveTableFromSQLiteMasterCache()                */
1185
/************************************************************************/
1186
1187
void GDALGeoPackageDataset::RemoveTableFromSQLiteMasterCache(
1188
    const char *pszTableName)
1189
0
{
1190
0
    m_oMapNameToType.erase(CPLString(pszTableName).toupper());
1191
0
}
1192
1193
/************************************************************************/
1194
/*                  GetUnknownExtensionsTableSpecific()                 */
1195
/************************************************************************/
1196
1197
const std::map<CPLString, std::vector<GPKGExtensionDesc>> &
1198
GDALGeoPackageDataset::GetUnknownExtensionsTableSpecific()
1199
1.12k
{
1200
1.12k
    if (m_bMapTableToExtensionsBuilt)
1201
754
        return m_oMapTableToExtensions;
1202
369
    m_bMapTableToExtensionsBuilt = true;
1203
1204
369
    if (!HasExtensionsTable())
1205
213
        return m_oMapTableToExtensions;
1206
1207
156
    CPLString osSQL(
1208
156
        "SELECT table_name, extension_name, definition, scope "
1209
156
        "FROM gpkg_extensions WHERE "
1210
156
        "table_name IS NOT NULL "
1211
156
        "AND extension_name IS NOT NULL "
1212
156
        "AND definition IS NOT NULL "
1213
156
        "AND scope IS NOT NULL "
1214
156
        "AND extension_name NOT IN ('gpkg_geom_CIRCULARSTRING', "
1215
156
        "'gpkg_geom_COMPOUNDCURVE', 'gpkg_geom_CURVEPOLYGON', "
1216
156
        "'gpkg_geom_MULTICURVE', "
1217
156
        "'gpkg_geom_MULTISURFACE', 'gpkg_geom_CURVE', 'gpkg_geom_SURFACE', "
1218
156
        "'gpkg_geom_POLYHEDRALSURFACE', 'gpkg_geom_TIN', 'gpkg_geom_TRIANGLE', "
1219
156
        "'gpkg_rtree_index', 'gpkg_geometry_type_trigger', "
1220
156
        "'gpkg_srs_id_trigger', "
1221
156
        "'gpkg_crs_wkt', 'gpkg_crs_wkt_1_1', 'gpkg_schema', "
1222
156
        "'gpkg_related_tables', 'related_tables'"
1223
#ifdef HAVE_SPATIALITE
1224
        ", 'gdal_spatialite_computed_geom_column'"
1225
#endif
1226
156
        ")");
1227
156
    const int nTableLimit = GetOGRTableLimit();
1228
156
    if (nTableLimit > 0)
1229
156
    {
1230
156
        osSQL += " LIMIT ";
1231
156
        osSQL += CPLSPrintf("%d", 1 + 10 * nTableLimit);
1232
156
    }
1233
1234
156
    auto oResult = SQLQuery(hDB, osSQL);
1235
156
    if (oResult)
1236
152
    {
1237
5.90k
        for (int i = 0; i < oResult->RowCount(); i++)
1238
5.74k
        {
1239
5.74k
            const char *pszTableName = oResult->GetValue(0, i);
1240
5.74k
            const char *pszExtensionName = oResult->GetValue(1, i);
1241
5.74k
            const char *pszDefinition = oResult->GetValue(2, i);
1242
5.74k
            const char *pszScope = oResult->GetValue(3, i);
1243
5.74k
            if (pszTableName && pszExtensionName && pszDefinition && pszScope)
1244
5.74k
            {
1245
5.74k
                GPKGExtensionDesc oDesc;
1246
5.74k
                oDesc.osExtensionName = pszExtensionName;
1247
5.74k
                oDesc.osDefinition = pszDefinition;
1248
5.74k
                oDesc.osScope = pszScope;
1249
5.74k
                m_oMapTableToExtensions[CPLString(pszTableName).toupper()]
1250
5.74k
                    .push_back(std::move(oDesc));
1251
5.74k
            }
1252
5.74k
        }
1253
152
    }
1254
1255
156
    return m_oMapTableToExtensions;
1256
369
}
1257
1258
/************************************************************************/
1259
/*                           GetContents()                              */
1260
/************************************************************************/
1261
1262
const std::map<CPLString, GPKGContentsDesc> &
1263
GDALGeoPackageDataset::GetContents()
1264
446
{
1265
446
    if (m_bMapTableToContentsBuilt)
1266
191
        return m_oMapTableToContents;
1267
255
    m_bMapTableToContentsBuilt = true;
1268
1269
255
    CPLString osSQL("SELECT table_name, data_type, identifier, "
1270
255
                    "description, min_x, min_y, max_x, max_y "
1271
255
                    "FROM gpkg_contents");
1272
255
    const int nTableLimit = GetOGRTableLimit();
1273
255
    if (nTableLimit > 0)
1274
255
    {
1275
255
        osSQL += " LIMIT ";
1276
255
        osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1277
255
    }
1278
1279
255
    auto oResult = SQLQuery(hDB, osSQL);
1280
255
    if (oResult)
1281
245
    {
1282
9.54k
        for (int i = 0; i < oResult->RowCount(); i++)
1283
9.29k
        {
1284
9.29k
            const char *pszTableName = oResult->GetValue(0, i);
1285
9.29k
            if (pszTableName == nullptr)
1286
560
                continue;
1287
8.73k
            const char *pszDataType = oResult->GetValue(1, i);
1288
8.73k
            const char *pszIdentifier = oResult->GetValue(2, i);
1289
8.73k
            const char *pszDescription = oResult->GetValue(3, i);
1290
8.73k
            const char *pszMinX = oResult->GetValue(4, i);
1291
8.73k
            const char *pszMinY = oResult->GetValue(5, i);
1292
8.73k
            const char *pszMaxX = oResult->GetValue(6, i);
1293
8.73k
            const char *pszMaxY = oResult->GetValue(7, i);
1294
8.73k
            GPKGContentsDesc oDesc;
1295
8.73k
            if (pszDataType)
1296
8.73k
                oDesc.osDataType = pszDataType;
1297
8.73k
            if (pszIdentifier)
1298
8.73k
                oDesc.osIdentifier = pszIdentifier;
1299
8.73k
            if (pszDescription)
1300
8.73k
                oDesc.osDescription = pszDescription;
1301
8.73k
            if (pszMinX)
1302
8.70k
                oDesc.osMinX = pszMinX;
1303
8.73k
            if (pszMinY)
1304
8.65k
                oDesc.osMinY = pszMinY;
1305
8.73k
            if (pszMaxX)
1306
8.60k
                oDesc.osMaxX = pszMaxX;
1307
8.73k
            if (pszMaxY)
1308
8.54k
                oDesc.osMaxY = pszMaxY;
1309
8.73k
            m_oMapTableToContents[CPLString(pszTableName).toupper()] =
1310
8.73k
                std::move(oDesc);
1311
8.73k
        }
1312
245
    }
1313
1314
255
    return m_oMapTableToContents;
1315
446
}
1316
1317
/************************************************************************/
1318
/*                                Open()                                */
1319
/************************************************************************/
1320
1321
int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
1322
                                const std::string &osFilenameInZip)
1323
2.09k
{
1324
2.09k
    m_osFilenameInZip = osFilenameInZip;
1325
2.09k
    CPLAssert(m_apoLayers.empty());
1326
2.09k
    CPLAssert(hDB == nullptr);
1327
1328
2.09k
    SetDescription(poOpenInfo->pszFilename);
1329
2.09k
    CPLString osFilename(poOpenInfo->pszFilename);
1330
2.09k
    CPLString osSubdatasetTableName;
1331
2.09k
    GByte abyHeaderLetMeHerePlease[100];
1332
2.09k
    const GByte *pabyHeader = poOpenInfo->pabyHeader;
1333
2.09k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GPKG:"))
1334
387
    {
1335
387
        char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename, ":",
1336
387
                                                CSLT_HONOURSTRINGS);
1337
387
        int nCount = CSLCount(papszTokens);
1338
387
        if (nCount < 2)
1339
0
        {
1340
0
            CSLDestroy(papszTokens);
1341
0
            return FALSE;
1342
0
        }
1343
1344
387
        if (nCount <= 3)
1345
112
        {
1346
112
            osFilename = papszTokens[1];
1347
112
        }
1348
        /* GPKG:C:\BLA.GPKG:foo */
1349
275
        else if (nCount == 4 && strlen(papszTokens[1]) == 1 &&
1350
0
                 (papszTokens[2][0] == '/' || papszTokens[2][0] == '\\'))
1351
0
        {
1352
0
            osFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1353
0
        }
1354
        // GPKG:/vsicurl/http[s]://[user:passwd@]example.com[:8080]/foo.gpkg:bar
1355
275
        else if (/*nCount >= 4 && */
1356
275
                 (EQUAL(papszTokens[1], "/vsicurl/http") ||
1357
92
                  EQUAL(papszTokens[1], "/vsicurl/https")))
1358
183
        {
1359
183
            osFilename = CPLString(papszTokens[1]);
1360
1.03k
            for (int i = 2; i < nCount - 1; i++)
1361
849
            {
1362
849
                osFilename += ':';
1363
849
                osFilename += papszTokens[i];
1364
849
            }
1365
183
        }
1366
387
        if (nCount >= 3)
1367
344
            osSubdatasetTableName = papszTokens[nCount - 1];
1368
1369
387
        CSLDestroy(papszTokens);
1370
387
        VSILFILE *fp = VSIFOpenL(osFilename, "rb");
1371
387
        if (fp != nullptr)
1372
194
        {
1373
194
            VSIFReadL(abyHeaderLetMeHerePlease, 1, 100, fp);
1374
194
            VSIFCloseL(fp);
1375
194
        }
1376
387
        pabyHeader = abyHeaderLetMeHerePlease;
1377
387
    }
1378
1.71k
    else if (poOpenInfo->pabyHeader &&
1379
1.71k
             STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1380
1.71k
                         "SQLite format 3"))
1381
47
    {
1382
47
        m_bCallUndeclareFileNotToOpen = true;
1383
47
        GDALOpenInfoDeclareFileNotToOpen(osFilename, poOpenInfo->pabyHeader,
1384
47
                                         poOpenInfo->nHeaderBytes);
1385
47
    }
1386
1387
2.09k
    eAccess = poOpenInfo->eAccess;
1388
2.09k
    if (!m_osFilenameInZip.empty())
1389
193
    {
1390
193
        m_pszFilename = CPLStrdup(CPLSPrintf(
1391
193
            "/vsizip/{%s}/%s", osFilename.c_str(), m_osFilenameInZip.c_str()));
1392
193
    }
1393
1.90k
    else
1394
1.90k
    {
1395
1.90k
        m_pszFilename = CPLStrdup(osFilename);
1396
1.90k
    }
1397
1398
2.09k
    if (poOpenInfo->papszOpenOptions)
1399
0
    {
1400
0
        CSLDestroy(papszOpenOptions);
1401
0
        papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1402
0
    }
1403
1404
2.09k
#ifdef ENABLE_SQL_GPKG_FORMAT
1405
2.09k
    if (poOpenInfo->pabyHeader &&
1406
1.71k
        STARTS_WITH(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
1407
2.09k
                    "-- SQL GPKG") &&
1408
1.47k
        poOpenInfo->fpL != nullptr)
1409
1.47k
    {
1410
1.47k
        if (sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr) !=
1411
1.47k
            SQLITE_OK)
1412
0
        {
1413
0
            return FALSE;
1414
0
        }
1415
1416
1.47k
        InstallSQLFunctions();
1417
1418
        // Ingest the lines of the dump
1419
1.47k
        VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET);
1420
1.47k
        const char *pszLine;
1421
2.92M
        while ((pszLine = CPLReadLineL(poOpenInfo->fpL)) != nullptr)
1422
2.92M
        {
1423
2.92M
            if (STARTS_WITH(pszLine, "--"))
1424
2.67k
                continue;
1425
1426
2.91M
            if (!SQLCheckLineIsSafe(pszLine))
1427
0
                return false;
1428
1429
2.91M
            char *pszErrMsg = nullptr;
1430
2.91M
            if (sqlite3_exec(hDB, pszLine, nullptr, nullptr, &pszErrMsg) !=
1431
2.91M
                SQLITE_OK)
1432
1.44M
            {
1433
1.44M
                if (pszErrMsg)
1434
1.44M
                    CPLDebug("SQLITE", "Error %s", pszErrMsg);
1435
1.44M
            }
1436
2.91M
            sqlite3_free(pszErrMsg);
1437
2.91M
        }
1438
1.47k
    }
1439
1440
627
    else if (pabyHeader != nullptr)
1441
627
#endif
1442
627
    {
1443
627
        if (poOpenInfo->fpL)
1444
240
        {
1445
            // See above comment about -wal locking for the importance of
1446
            // closing that file, prior to calling sqlite3_open()
1447
240
            VSIFCloseL(poOpenInfo->fpL);
1448
240
            poOpenInfo->fpL = nullptr;
1449
240
        }
1450
1451
        /* See if we can open the SQLite database */
1452
627
        if (!OpenOrCreateDB(GetUpdate() ? SQLITE_OPEN_READWRITE
1453
627
                                        : SQLITE_OPEN_READONLY))
1454
201
            return FALSE;
1455
1456
426
        memcpy(&m_nApplicationId, pabyHeader + knApplicationIdPos, 4);
1457
426
        m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
1458
426
        memcpy(&m_nUserVersion, pabyHeader + knUserVersionPos, 4);
1459
426
        m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
1460
426
        if (m_nApplicationId == GP10_APPLICATION_ID)
1461
6
        {
1462
6
            CPLDebug("GPKG", "GeoPackage v1.0");
1463
6
        }
1464
420
        else if (m_nApplicationId == GP11_APPLICATION_ID)
1465
0
        {
1466
0
            CPLDebug("GPKG", "GeoPackage v1.1");
1467
0
        }
1468
420
        else if (m_nApplicationId == GPKG_APPLICATION_ID &&
1469
37
                 m_nUserVersion >= GPKG_1_2_VERSION)
1470
37
        {
1471
37
            CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
1472
37
                     (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
1473
37
        }
1474
426
    }
1475
1476
    /* Requirement 6: The SQLite PRAGMA integrity_check SQL command SHALL return
1477
     * “ok” */
1478
    /* http://opengis.github.io/geopackage/#_file_integrity */
1479
    /* Disable integrity check by default, since it is expensive on big files */
1480
1.89k
    if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_INTEGRITY_CHECK", "NO")) &&
1481
0
        OGRERR_NONE != PragmaCheck("integrity_check", "ok", 1))
1482
0
    {
1483
0
        CPLError(CE_Failure, CPLE_AppDefined,
1484
0
                 "pragma integrity_check on '%s' failed", m_pszFilename);
1485
0
        return FALSE;
1486
0
    }
1487
1488
    /* Requirement 7: The SQLite PRAGMA foreign_key_check() SQL with no */
1489
    /* parameter value SHALL return an empty result set */
1490
    /* http://opengis.github.io/geopackage/#_file_integrity */
1491
    /* Disable the check by default, since it is to corrupt databases, and */
1492
    /* that causes issues to downstream software that can't open them. */
1493
1.89k
    if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_FOREIGN_KEY_CHECK", "NO")) &&
1494
0
        OGRERR_NONE != PragmaCheck("foreign_key_check", "", 0))
1495
0
    {
1496
0
        CPLError(CE_Failure, CPLE_AppDefined,
1497
0
                 "pragma foreign_key_check on '%s' failed.", m_pszFilename);
1498
0
        return FALSE;
1499
0
    }
1500
1501
    /* Check for requirement metadata tables */
1502
    /* Requirement 10: gpkg_spatial_ref_sys must exist */
1503
    /* Requirement 13: gpkg_contents must exist */
1504
1.89k
    if (SQLGetInteger(hDB,
1505
1.89k
                      "SELECT COUNT(*) FROM sqlite_master WHERE "
1506
1.89k
                      "name IN ('gpkg_spatial_ref_sys', 'gpkg_contents') AND "
1507
1.89k
                      "type IN ('table', 'view')",
1508
1.89k
                      nullptr) != 2)
1509
1.26k
    {
1510
1.26k
        CPLError(CE_Failure, CPLE_AppDefined,
1511
1.26k
                 "At least one of the required GeoPackage tables, "
1512
1.26k
                 "gpkg_spatial_ref_sys or gpkg_contents, is missing");
1513
1.26k
        return FALSE;
1514
1.26k
    }
1515
1516
629
    DetectSpatialRefSysColumns();
1517
1518
629
#ifdef ENABLE_GPKG_OGR_CONTENTS
1519
629
    if (SQLGetInteger(hDB,
1520
629
                      "SELECT 1 FROM sqlite_master WHERE "
1521
629
                      "name = 'gpkg_ogr_contents' AND type = 'table'",
1522
629
                      nullptr) == 1)
1523
418
    {
1524
418
        m_bHasGPKGOGRContents = true;
1525
418
    }
1526
629
#endif
1527
1528
629
    CheckUnknownExtensions();
1529
1530
629
    int bRet = FALSE;
1531
629
    bool bHasGPKGExtRelations = false;
1532
629
    if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
1533
590
    {
1534
590
        m_bHasGPKGGeometryColumns =
1535
590
            SQLGetInteger(hDB,
1536
590
                          "SELECT 1 FROM sqlite_master WHERE "
1537
590
                          "name = 'gpkg_geometry_columns' AND "
1538
590
                          "type IN ('table', 'view')",
1539
590
                          nullptr) == 1;
1540
590
        bHasGPKGExtRelations = HasGpkgextRelationsTable();
1541
590
    }
1542
629
    if (m_bHasGPKGGeometryColumns)
1543
585
    {
1544
        /* Load layer definitions for all tables in gpkg_contents &
1545
         * gpkg_geometry_columns */
1546
        /* and non-spatial tables as well */
1547
585
        std::string osSQL =
1548
585
            "SELECT c.table_name, c.identifier, 1 as is_spatial, "
1549
585
            "g.column_name, g.geometry_type_name, g.z, g.m, c.min_x, c.min_y, "
1550
585
            "c.max_x, c.max_y, 1 AS is_in_gpkg_contents, "
1551
585
            "(SELECT type FROM sqlite_master WHERE lower(name) = "
1552
585
            "lower(c.table_name) AND type IN ('table', 'view')) AS object_type "
1553
585
            "  FROM gpkg_geometry_columns g "
1554
585
            "  JOIN gpkg_contents c ON (g.table_name = c.table_name)"
1555
585
            "  WHERE "
1556
585
            "  c.table_name <> 'ogr_empty_table' AND"
1557
585
            "  c.data_type = 'features' "
1558
            // aspatial: Was the only method available in OGR 2.0 and 2.1
1559
            // attributes: GPKG 1.2 or later
1560
585
            "UNION ALL "
1561
585
            "SELECT table_name, identifier, 0 as is_spatial, NULL, NULL, 0, 0, "
1562
585
            "0 AS xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 1 AS "
1563
585
            "is_in_gpkg_contents, "
1564
585
            "(SELECT type FROM sqlite_master WHERE lower(name) = "
1565
585
            "lower(table_name) AND type IN ('table', 'view')) AS object_type "
1566
585
            "  FROM gpkg_contents"
1567
585
            "  WHERE data_type IN ('aspatial', 'attributes') ";
1568
1569
585
        const char *pszListAllTables = CSLFetchNameValueDef(
1570
585
            poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "AUTO");
1571
585
        bool bHasASpatialOrAttributes = HasGDALAspatialExtension();
1572
585
        if (!bHasASpatialOrAttributes)
1573
585
        {
1574
585
            auto oResultTable =
1575
585
                SQLQuery(hDB, "SELECT * FROM gpkg_contents WHERE "
1576
585
                              "data_type = 'attributes' LIMIT 1");
1577
585
            bHasASpatialOrAttributes =
1578
585
                (oResultTable && oResultTable->RowCount() == 1);
1579
585
        }
1580
585
        if (bHasGPKGExtRelations)
1581
1
        {
1582
1
            osSQL += "UNION ALL "
1583
1
                     "SELECT mapping_table_name, mapping_table_name, 0 as "
1584
1
                     "is_spatial, NULL, NULL, 0, 0, 0 AS "
1585
1
                     "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1586
1
                     "is_in_gpkg_contents, 'table' AS object_type "
1587
1
                     "FROM gpkgext_relations WHERE "
1588
1
                     "lower(mapping_table_name) NOT IN (SELECT "
1589
1
                     "lower(table_name) FROM gpkg_contents) AND "
1590
1
                     "EXISTS (SELECT 1 FROM sqlite_master WHERE "
1591
1
                     "type IN ('table', 'view') AND "
1592
1
                     "lower(name) = lower(mapping_table_name))";
1593
1
        }
1594
585
        if (EQUAL(pszListAllTables, "YES") ||
1595
585
            (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
1596
393
        {
1597
            // vgpkg_ is Spatialite virtual table
1598
393
            osSQL +=
1599
393
                "UNION ALL "
1600
393
                "SELECT name, name, 0 as is_spatial, NULL, NULL, 0, 0, 0 AS "
1601
393
                "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS "
1602
393
                "is_in_gpkg_contents, type AS object_type "
1603
393
                "FROM sqlite_master WHERE type IN ('table', 'view') "
1604
393
                "AND name NOT LIKE 'gpkg_%' "
1605
393
                "AND name NOT LIKE 'vgpkg_%' "
1606
393
                "AND name NOT LIKE 'rtree_%' AND name NOT LIKE 'sqlite_%' "
1607
                // Avoid reading those views from simple_sewer_features.gpkg
1608
393
                "AND name NOT IN ('st_spatial_ref_sys', 'spatial_ref_sys', "
1609
393
                "'st_geometry_columns', 'geometry_columns') "
1610
393
                "AND lower(name) NOT IN (SELECT lower(table_name) FROM "
1611
393
                "gpkg_contents)";
1612
393
            if (bHasGPKGExtRelations)
1613
1
            {
1614
1
                osSQL += " AND lower(name) NOT IN (SELECT "
1615
1
                         "lower(mapping_table_name) FROM "
1616
1
                         "gpkgext_relations)";
1617
1
            }
1618
393
        }
1619
585
        const int nTableLimit = GetOGRTableLimit();
1620
585
        if (nTableLimit > 0)
1621
585
        {
1622
585
            osSQL += " LIMIT ";
1623
585
            osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1624
585
        }
1625
1626
585
        auto oResult = SQLQuery(hDB, osSQL.c_str());
1627
585
        if (!oResult)
1628
0
        {
1629
0
            return FALSE;
1630
0
        }
1631
1632
585
        if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1633
5
        {
1634
5
            CPLError(CE_Warning, CPLE_AppDefined,
1635
5
                     "File has more than %d vector tables. "
1636
5
                     "Limiting to first %d (can be overridden with "
1637
5
                     "OGR_TABLE_LIMIT config option)",
1638
5
                     nTableLimit, nTableLimit);
1639
5
            oResult->LimitRowCount(nTableLimit);
1640
5
        }
1641
1642
585
        if (oResult->RowCount() > 0)
1643
581
        {
1644
581
            bRet = TRUE;
1645
1646
581
            m_apoLayers.reserve(oResult->RowCount());
1647
1648
581
            std::map<std::string, int> oMapTableRefCount;
1649
130k
            for (int i = 0; i < oResult->RowCount(); i++)
1650
130k
            {
1651
130k
                const char *pszTableName = oResult->GetValue(0, i);
1652
130k
                if (pszTableName == nullptr)
1653
0
                    continue;
1654
130k
                if (++oMapTableRefCount[pszTableName] == 2)
1655
220
                {
1656
                    // This should normally not happen if all constraints are
1657
                    // properly set
1658
220
                    CPLError(CE_Warning, CPLE_AppDefined,
1659
220
                             "Table %s appearing several times in "
1660
220
                             "gpkg_contents and/or gpkg_geometry_columns",
1661
220
                             pszTableName);
1662
220
                }
1663
130k
            }
1664
1665
581
            std::set<std::string> oExistingLayers;
1666
130k
            for (int i = 0; i < oResult->RowCount(); i++)
1667
130k
            {
1668
130k
                const char *pszTableName = oResult->GetValue(0, i);
1669
130k
                if (pszTableName == nullptr)
1670
0
                    continue;
1671
130k
                const bool bTableHasSeveralGeomColumns =
1672
130k
                    oMapTableRefCount[pszTableName] > 1;
1673
130k
                bool bIsSpatial = CPL_TO_BOOL(oResult->GetValueAsInteger(2, i));
1674
130k
                const char *pszGeomColName = oResult->GetValue(3, i);
1675
130k
                const char *pszGeomType = oResult->GetValue(4, i);
1676
130k
                const char *pszZ = oResult->GetValue(5, i);
1677
130k
                const char *pszM = oResult->GetValue(6, i);
1678
130k
                bool bIsInGpkgContents =
1679
130k
                    CPL_TO_BOOL(oResult->GetValueAsInteger(11, i));
1680
130k
                if (!bIsInGpkgContents)
1681
748
                    m_bNonSpatialTablesNonRegisteredInGpkgContentsFound = true;
1682
130k
                const char *pszObjectType = oResult->GetValue(12, i);
1683
130k
                if (pszObjectType == nullptr ||
1684
129k
                    !(EQUAL(pszObjectType, "table") ||
1685
0
                      EQUAL(pszObjectType, "view")))
1686
176
                {
1687
176
                    CPLError(CE_Warning, CPLE_AppDefined,
1688
176
                             "Table/view %s is referenced in gpkg_contents, "
1689
176
                             "but does not exist",
1690
176
                             pszTableName);
1691
176
                    continue;
1692
176
                }
1693
                // Non-standard and undocumented behavior:
1694
                // if the same table appears to have several geometry columns,
1695
                // handle it for now as multiple layers named
1696
                // "table_name (geom_col_name)"
1697
                // The way we handle that might change in the future (e.g
1698
                // could be a single layer with multiple geometry columns)
1699
129k
                std::string osLayerNameWithGeomColName =
1700
129k
                    pszGeomColName ? std::string(pszTableName) + " (" +
1701
127k
                                         pszGeomColName + ')'
1702
129k
                                   : std::string(pszTableName);
1703
129k
                if (cpl::contains(oExistingLayers, osLayerNameWithGeomColName))
1704
126k
                    continue;
1705
3.08k
                oExistingLayers.insert(osLayerNameWithGeomColName);
1706
3.08k
                const std::string osLayerName =
1707
3.08k
                    bTableHasSeveralGeomColumns
1708
3.08k
                        ? std::move(osLayerNameWithGeomColName)
1709
3.08k
                        : std::string(pszTableName);
1710
3.08k
                auto poLayer = std::make_unique<OGRGeoPackageTableLayer>(
1711
3.08k
                    this, osLayerName.c_str());
1712
3.08k
                bool bHasZ = pszZ && atoi(pszZ) > 0;
1713
3.08k
                bool bHasM = pszM && atoi(pszM) > 0;
1714
3.08k
                if (pszGeomType && EQUAL(pszGeomType, "GEOMETRY"))
1715
480
                {
1716
480
                    if (pszZ && atoi(pszZ) == 2)
1717
42
                        bHasZ = false;
1718
480
                    if (pszM && atoi(pszM) == 2)
1719
17
                        bHasM = false;
1720
480
                }
1721
3.08k
                poLayer->SetOpeningParameters(
1722
3.08k
                    pszTableName, pszObjectType, bIsInGpkgContents, bIsSpatial,
1723
3.08k
                    pszGeomColName, pszGeomType, bHasZ, bHasM);
1724
3.08k
                m_apoLayers.push_back(std::move(poLayer));
1725
3.08k
            }
1726
581
        }
1727
585
    }
1728
1729
629
    bool bHasTileMatrixSet = false;
1730
629
    if (poOpenInfo->nOpenFlags & GDAL_OF_RASTER)
1731
39
    {
1732
39
        bHasTileMatrixSet = SQLGetInteger(hDB,
1733
39
                                          "SELECT 1 FROM sqlite_master WHERE "
1734
39
                                          "name = 'gpkg_tile_matrix_set' AND "
1735
39
                                          "type IN ('table', 'view')",
1736
39
                                          nullptr) == 1;
1737
39
    }
1738
629
    if (bHasTileMatrixSet)
1739
37
    {
1740
37
        std::string osSQL =
1741
37
            "SELECT c.table_name, c.identifier, c.description, c.srs_id, "
1742
37
            "c.min_x, c.min_y, c.max_x, c.max_y, "
1743
37
            "tms.min_x, tms.min_y, tms.max_x, tms.max_y, c.data_type "
1744
37
            "FROM gpkg_contents c JOIN gpkg_tile_matrix_set tms ON "
1745
37
            "c.table_name = tms.table_name WHERE "
1746
37
            "data_type IN ('tiles', '2d-gridded-coverage')";
1747
37
        if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE"))
1748
0
            osSubdatasetTableName =
1749
0
                CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TABLE");
1750
37
        if (!osSubdatasetTableName.empty())
1751
0
        {
1752
0
            char *pszTmp = sqlite3_mprintf(" AND c.table_name='%q'",
1753
0
                                           osSubdatasetTableName.c_str());
1754
0
            osSQL += pszTmp;
1755
0
            sqlite3_free(pszTmp);
1756
0
            SetPhysicalFilename(osFilename.c_str());
1757
0
        }
1758
37
        const int nTableLimit = GetOGRTableLimit();
1759
37
        if (nTableLimit > 0)
1760
37
        {
1761
37
            osSQL += " LIMIT ";
1762
37
            osSQL += CPLSPrintf("%d", 1 + nTableLimit);
1763
37
        }
1764
1765
37
        auto oResult = SQLQuery(hDB, osSQL.c_str());
1766
37
        if (!oResult)
1767
1
        {
1768
1
            return FALSE;
1769
1
        }
1770
1771
36
        if (oResult->RowCount() == 0 && !osSubdatasetTableName.empty())
1772
0
        {
1773
0
            CPLError(CE_Failure, CPLE_AppDefined,
1774
0
                     "Cannot find table '%s' in GeoPackage dataset",
1775
0
                     osSubdatasetTableName.c_str());
1776
0
        }
1777
36
        else if (oResult->RowCount() == 1)
1778
26
        {
1779
26
            const char *pszTableName = oResult->GetValue(0, 0);
1780
26
            const char *pszIdentifier = oResult->GetValue(1, 0);
1781
26
            const char *pszDescription = oResult->GetValue(2, 0);
1782
26
            const char *pszSRSId = oResult->GetValue(3, 0);
1783
26
            const char *pszMinX = oResult->GetValue(4, 0);
1784
26
            const char *pszMinY = oResult->GetValue(5, 0);
1785
26
            const char *pszMaxX = oResult->GetValue(6, 0);
1786
26
            const char *pszMaxY = oResult->GetValue(7, 0);
1787
26
            const char *pszTMSMinX = oResult->GetValue(8, 0);
1788
26
            const char *pszTMSMinY = oResult->GetValue(9, 0);
1789
26
            const char *pszTMSMaxX = oResult->GetValue(10, 0);
1790
26
            const char *pszTMSMaxY = oResult->GetValue(11, 0);
1791
26
            const char *pszDataType = oResult->GetValue(12, 0);
1792
26
            if (pszTableName && pszTMSMinX && pszTMSMinY && pszTMSMaxX &&
1793
25
                pszTMSMaxY)
1794
25
            {
1795
25
                bRet = OpenRaster(
1796
25
                    pszTableName, pszIdentifier, pszDescription,
1797
25
                    pszSRSId ? atoi(pszSRSId) : 0, CPLAtof(pszTMSMinX),
1798
25
                    CPLAtof(pszTMSMinY), CPLAtof(pszTMSMaxX),
1799
25
                    CPLAtof(pszTMSMaxY), pszMinX, pszMinY, pszMaxX, pszMaxY,
1800
25
                    EQUAL(pszDataType, "tiles"), poOpenInfo->papszOpenOptions);
1801
25
            }
1802
26
        }
1803
10
        else if (oResult->RowCount() >= 1)
1804
4
        {
1805
4
            bRet = TRUE;
1806
1807
4
            if (nTableLimit > 0 && oResult->RowCount() > nTableLimit)
1808
0
            {
1809
0
                CPLError(CE_Warning, CPLE_AppDefined,
1810
0
                         "File has more than %d raster tables. "
1811
0
                         "Limiting to first %d (can be overridden with "
1812
0
                         "OGR_TABLE_LIMIT config option)",
1813
0
                         nTableLimit, nTableLimit);
1814
0
                oResult->LimitRowCount(nTableLimit);
1815
0
            }
1816
1817
4
            int nSDSCount = 0;
1818
43
            for (int i = 0; i < oResult->RowCount(); i++)
1819
39
            {
1820
39
                const char *pszTableName = oResult->GetValue(0, i);
1821
39
                const char *pszIdentifier = oResult->GetValue(1, i);
1822
39
                if (pszTableName == nullptr)
1823
0
                    continue;
1824
39
                m_aosSubDatasets.AddNameValue(
1825
39
                    CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
1826
39
                    CPLSPrintf("GPKG:%s:%s", m_pszFilename, pszTableName));
1827
39
                m_aosSubDatasets.AddNameValue(
1828
39
                    CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
1829
39
                    pszIdentifier
1830
39
                        ? CPLSPrintf("%s - %s", pszTableName, pszIdentifier)
1831
39
                        : pszTableName);
1832
39
                nSDSCount++;
1833
39
            }
1834
4
        }
1835
36
    }
1836
1837
628
    if (!bRet && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR))
1838
9
    {
1839
9
        if ((poOpenInfo->nOpenFlags & GDAL_OF_UPDATE))
1840
0
        {
1841
0
            bRet = TRUE;
1842
0
        }
1843
9
        else
1844
9
        {
1845
9
            CPLDebug("GPKG",
1846
9
                     "This GeoPackage has no vector content and is opened "
1847
9
                     "in read-only mode. If you open it in update mode, "
1848
9
                     "opening will be successful.");
1849
9
        }
1850
9
    }
1851
1852
628
    if (eAccess == GA_Update)
1853
0
    {
1854
0
        FixupWrongRTreeTrigger();
1855
0
        FixupWrongMedataReferenceColumnNameUpdate();
1856
0
    }
1857
1858
628
    SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
1859
1860
628
    return bRet;
1861
629
}
1862
1863
/************************************************************************/
1864
/*                    DetectSpatialRefSysColumns()                      */
1865
/************************************************************************/
1866
1867
void GDALGeoPackageDataset::DetectSpatialRefSysColumns()
1868
629
{
1869
    // Detect definition_12_063 column
1870
629
    {
1871
629
        sqlite3_stmt *hSQLStmt = nullptr;
1872
629
        int rc = sqlite3_prepare_v2(
1873
629
            hDB, "SELECT definition_12_063 FROM gpkg_spatial_ref_sys ", -1,
1874
629
            &hSQLStmt, nullptr);
1875
629
        if (rc == SQLITE_OK)
1876
15
        {
1877
15
            m_bHasDefinition12_063 = true;
1878
15
            sqlite3_finalize(hSQLStmt);
1879
15
        }
1880
629
    }
1881
1882
    // Detect epoch column
1883
629
    if (m_bHasDefinition12_063)
1884
15
    {
1885
15
        sqlite3_stmt *hSQLStmt = nullptr;
1886
15
        int rc =
1887
15
            sqlite3_prepare_v2(hDB, "SELECT epoch FROM gpkg_spatial_ref_sys ",
1888
15
                               -1, &hSQLStmt, nullptr);
1889
15
        if (rc == SQLITE_OK)
1890
15
        {
1891
15
            m_bHasEpochColumn = true;
1892
15
            sqlite3_finalize(hSQLStmt);
1893
15
        }
1894
15
    }
1895
629
}
1896
1897
/************************************************************************/
1898
/*                    FixupWrongRTreeTrigger()                          */
1899
/************************************************************************/
1900
1901
void GDALGeoPackageDataset::FixupWrongRTreeTrigger()
1902
0
{
1903
0
    auto oResult = SQLQuery(
1904
0
        hDB,
1905
0
        "SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND "
1906
0
        "NAME LIKE 'rtree_%_update3' AND sql LIKE '% AFTER UPDATE OF % ON %'");
1907
0
    if (oResult == nullptr)
1908
0
        return;
1909
0
    if (oResult->RowCount() > 0)
1910
0
    {
1911
0
        CPLDebug("GPKG", "Fixing incorrect trigger(s) related to RTree");
1912
0
    }
1913
0
    for (int i = 0; i < oResult->RowCount(); i++)
1914
0
    {
1915
0
        const char *pszName = oResult->GetValue(0, i);
1916
0
        const char *pszSQL = oResult->GetValue(1, i);
1917
0
        const char *pszPtr1 = strstr(pszSQL, " AFTER UPDATE OF ");
1918
0
        if (pszPtr1)
1919
0
        {
1920
0
            const char *pszPtr = pszPtr1 + strlen(" AFTER UPDATE OF ");
1921
            // Skipping over geometry column name
1922
0
            while (*pszPtr == ' ')
1923
0
                pszPtr++;
1924
0
            if (pszPtr[0] == '"' || pszPtr[0] == '\'')
1925
0
            {
1926
0
                char chStringDelim = pszPtr[0];
1927
0
                pszPtr++;
1928
0
                while (*pszPtr != '\0' && *pszPtr != chStringDelim)
1929
0
                {
1930
0
                    if (*pszPtr == '\\' && pszPtr[1] == chStringDelim)
1931
0
                        pszPtr += 2;
1932
0
                    else
1933
0
                        pszPtr += 1;
1934
0
                }
1935
0
                if (*pszPtr == chStringDelim)
1936
0
                    pszPtr++;
1937
0
            }
1938
0
            else
1939
0
            {
1940
0
                pszPtr++;
1941
0
                while (*pszPtr != ' ')
1942
0
                    pszPtr++;
1943
0
            }
1944
0
            if (*pszPtr == ' ')
1945
0
            {
1946
0
                SQLCommand(hDB,
1947
0
                           ("DROP TRIGGER \"" + SQLEscapeName(pszName) + "\"")
1948
0
                               .c_str());
1949
0
                CPLString newSQL;
1950
0
                newSQL.assign(pszSQL, pszPtr1 - pszSQL);
1951
0
                newSQL += " AFTER UPDATE";
1952
0
                newSQL += pszPtr;
1953
0
                SQLCommand(hDB, newSQL);
1954
0
            }
1955
0
        }
1956
0
    }
1957
0
}
1958
1959
/************************************************************************/
1960
/*             FixupWrongMedataReferenceColumnNameUpdate()              */
1961
/************************************************************************/
1962
1963
void GDALGeoPackageDataset::FixupWrongMedataReferenceColumnNameUpdate()
1964
0
{
1965
    // Fix wrong trigger that was generated by GDAL < 2.4.0
1966
    // See https://github.com/qgis/QGIS/issues/42768
1967
0
    auto oResult = SQLQuery(
1968
0
        hDB, "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND "
1969
0
             "NAME ='gpkg_metadata_reference_column_name_update' AND "
1970
0
             "sql LIKE '%column_nameIS%'");
1971
0
    if (oResult == nullptr)
1972
0
        return;
1973
0
    if (oResult->RowCount() == 1)
1974
0
    {
1975
0
        CPLDebug("GPKG", "Fixing incorrect trigger "
1976
0
                         "gpkg_metadata_reference_column_name_update");
1977
0
        const char *pszSQL = oResult->GetValue(0, 0);
1978
0
        std::string osNewSQL(
1979
0
            CPLString(pszSQL).replaceAll("column_nameIS", "column_name IS"));
1980
1981
0
        SQLCommand(hDB,
1982
0
                   "DROP TRIGGER gpkg_metadata_reference_column_name_update");
1983
0
        SQLCommand(hDB, osNewSQL.c_str());
1984
0
    }
1985
0
}
1986
1987
/************************************************************************/
1988
/*                  ClearCachedRelationships()                          */
1989
/************************************************************************/
1990
1991
void GDALGeoPackageDataset::ClearCachedRelationships()
1992
0
{
1993
0
    m_bHasPopulatedRelationships = false;
1994
0
    m_osMapRelationships.clear();
1995
0
}
1996
1997
/************************************************************************/
1998
/*                           LoadRelationships()                        */
1999
/************************************************************************/
2000
2001
void GDALGeoPackageDataset::LoadRelationships() const
2002
0
{
2003
0
    m_osMapRelationships.clear();
2004
2005
0
    std::vector<std::string> oExcludedTables;
2006
0
    if (HasGpkgextRelationsTable())
2007
0
    {
2008
0
        LoadRelationshipsUsingRelatedTablesExtension();
2009
2010
0
        for (const auto &oRelationship : m_osMapRelationships)
2011
0
        {
2012
0
            oExcludedTables.emplace_back(
2013
0
                oRelationship.second->GetMappingTableName());
2014
0
        }
2015
0
    }
2016
2017
    // Also load relationships defined using foreign keys (i.e. one-to-many
2018
    // relationships). Here we must exclude any relationships defined from the
2019
    // related tables extension, we don't want them included twice.
2020
0
    LoadRelationshipsFromForeignKeys(oExcludedTables);
2021
0
    m_bHasPopulatedRelationships = true;
2022
0
}
2023
2024
/************************************************************************/
2025
/*         LoadRelationshipsUsingRelatedTablesExtension()               */
2026
/************************************************************************/
2027
2028
void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
2029
0
{
2030
0
    m_osMapRelationships.clear();
2031
2032
0
    auto oResultTable = SQLQuery(
2033
0
        hDB, "SELECT base_table_name, base_primary_column, "
2034
0
             "related_table_name, related_primary_column, relation_name, "
2035
0
             "mapping_table_name FROM gpkgext_relations");
2036
0
    if (oResultTable && oResultTable->RowCount() > 0)
2037
0
    {
2038
0
        for (int i = 0; i < oResultTable->RowCount(); i++)
2039
0
        {
2040
0
            const char *pszBaseTableName = oResultTable->GetValue(0, i);
2041
0
            if (!pszBaseTableName)
2042
0
            {
2043
0
                CPLError(CE_Warning, CPLE_AppDefined,
2044
0
                         "Could not retrieve base_table_name from "
2045
0
                         "gpkgext_relations");
2046
0
                continue;
2047
0
            }
2048
0
            const char *pszBasePrimaryColumn = oResultTable->GetValue(1, i);
2049
0
            if (!pszBasePrimaryColumn)
2050
0
            {
2051
0
                CPLError(CE_Warning, CPLE_AppDefined,
2052
0
                         "Could not retrieve base_primary_column from "
2053
0
                         "gpkgext_relations");
2054
0
                continue;
2055
0
            }
2056
0
            const char *pszRelatedTableName = oResultTable->GetValue(2, i);
2057
0
            if (!pszRelatedTableName)
2058
0
            {
2059
0
                CPLError(CE_Warning, CPLE_AppDefined,
2060
0
                         "Could not retrieve related_table_name from "
2061
0
                         "gpkgext_relations");
2062
0
                continue;
2063
0
            }
2064
0
            const char *pszRelatedPrimaryColumn = oResultTable->GetValue(3, i);
2065
0
            if (!pszRelatedPrimaryColumn)
2066
0
            {
2067
0
                CPLError(CE_Warning, CPLE_AppDefined,
2068
0
                         "Could not retrieve related_primary_column from "
2069
0
                         "gpkgext_relations");
2070
0
                continue;
2071
0
            }
2072
0
            const char *pszRelationName = oResultTable->GetValue(4, i);
2073
0
            if (!pszRelationName)
2074
0
            {
2075
0
                CPLError(
2076
0
                    CE_Warning, CPLE_AppDefined,
2077
0
                    "Could not retrieve relation_name from gpkgext_relations");
2078
0
                continue;
2079
0
            }
2080
0
            const char *pszMappingTableName = oResultTable->GetValue(5, i);
2081
0
            if (!pszMappingTableName)
2082
0
            {
2083
0
                CPLError(CE_Warning, CPLE_AppDefined,
2084
0
                         "Could not retrieve mapping_table_name from "
2085
0
                         "gpkgext_relations");
2086
0
                continue;
2087
0
            }
2088
2089
            // confirm that mapping table exists
2090
0
            char *pszSQL =
2091
0
                sqlite3_mprintf("SELECT 1 FROM sqlite_master WHERE "
2092
0
                                "name='%q' AND type IN ('table', 'view')",
2093
0
                                pszMappingTableName);
2094
0
            const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
2095
0
            sqlite3_free(pszSQL);
2096
2097
0
            if (nMappingTableCount < 1 &&
2098
0
                !const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
2099
0
                    pszMappingTableName))
2100
0
            {
2101
0
                CPLError(CE_Warning, CPLE_AppDefined,
2102
0
                         "Relationship mapping table %s does not exist",
2103
0
                         pszMappingTableName);
2104
0
                continue;
2105
0
            }
2106
2107
0
            const std::string osRelationName = GenerateNameForRelationship(
2108
0
                pszBaseTableName, pszRelatedTableName, pszRelationName);
2109
2110
0
            std::string osType{};
2111
            // defined requirement classes -- for these types the relation name
2112
            // will be specific string value from the related tables extension.
2113
            // In this case we need to construct a unique relationship name
2114
            // based on the related tables
2115
0
            if (EQUAL(pszRelationName, "media") ||
2116
0
                EQUAL(pszRelationName, "simple_attributes") ||
2117
0
                EQUAL(pszRelationName, "features") ||
2118
0
                EQUAL(pszRelationName, "attributes") ||
2119
0
                EQUAL(pszRelationName, "tiles"))
2120
0
            {
2121
0
                osType = pszRelationName;
2122
0
            }
2123
0
            else
2124
0
            {
2125
                // user defined types default to features
2126
0
                osType = "features";
2127
0
            }
2128
2129
0
            auto poRelationship = std::make_unique<GDALRelationship>(
2130
0
                osRelationName, pszBaseTableName, pszRelatedTableName,
2131
0
                GRC_MANY_TO_MANY);
2132
2133
0
            poRelationship->SetLeftTableFields({pszBasePrimaryColumn});
2134
0
            poRelationship->SetRightTableFields({pszRelatedPrimaryColumn});
2135
0
            poRelationship->SetLeftMappingTableFields({"base_id"});
2136
0
            poRelationship->SetRightMappingTableFields({"related_id"});
2137
0
            poRelationship->SetMappingTableName(pszMappingTableName);
2138
0
            poRelationship->SetRelatedTableType(osType);
2139
2140
0
            m_osMapRelationships[osRelationName] = std::move(poRelationship);
2141
0
        }
2142
0
    }
2143
0
}
2144
2145
/************************************************************************/
2146
/*                GenerateNameForRelationship()                         */
2147
/************************************************************************/
2148
2149
std::string GDALGeoPackageDataset::GenerateNameForRelationship(
2150
    const char *pszBaseTableName, const char *pszRelatedTableName,
2151
    const char *pszType)
2152
0
{
2153
    // defined requirement classes -- for these types the relation name will be
2154
    // specific string value from the related tables extension. In this case we
2155
    // need to construct a unique relationship name based on the related tables
2156
0
    if (EQUAL(pszType, "media") || EQUAL(pszType, "simple_attributes") ||
2157
0
        EQUAL(pszType, "features") || EQUAL(pszType, "attributes") ||
2158
0
        EQUAL(pszType, "tiles"))
2159
0
    {
2160
0
        std::ostringstream stream;
2161
0
        stream << pszBaseTableName << '_' << pszRelatedTableName << '_'
2162
0
               << pszType;
2163
0
        return stream.str();
2164
0
    }
2165
0
    else
2166
0
    {
2167
        // user defined types default to features
2168
0
        return pszType;
2169
0
    }
2170
0
}
2171
2172
/************************************************************************/
2173
/*                       ValidateRelationship()                         */
2174
/************************************************************************/
2175
2176
bool GDALGeoPackageDataset::ValidateRelationship(
2177
    const GDALRelationship *poRelationship, std::string &failureReason)
2178
0
{
2179
2180
0
    if (poRelationship->GetCardinality() !=
2181
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2182
0
    {
2183
0
        failureReason = "Only many to many relationships are supported";
2184
0
        return false;
2185
0
    }
2186
2187
0
    std::string osRelatedTableType = poRelationship->GetRelatedTableType();
2188
0
    if (!osRelatedTableType.empty() && osRelatedTableType != "features" &&
2189
0
        osRelatedTableType != "media" &&
2190
0
        osRelatedTableType != "simple_attributes" &&
2191
0
        osRelatedTableType != "attributes" && osRelatedTableType != "tiles")
2192
0
    {
2193
0
        failureReason =
2194
0
            ("Related table type " + osRelatedTableType +
2195
0
             " is not a valid value for the GeoPackage specification. "
2196
0
             "Valid values are: features, media, simple_attributes, "
2197
0
             "attributes, tiles.")
2198
0
                .c_str();
2199
0
        return false;
2200
0
    }
2201
2202
0
    const std::string &osLeftTableName = poRelationship->GetLeftTableName();
2203
0
    OGRGeoPackageLayer *poLeftTable = cpl::down_cast<OGRGeoPackageLayer *>(
2204
0
        GetLayerByName(osLeftTableName.c_str()));
2205
0
    if (!poLeftTable)
2206
0
    {
2207
0
        failureReason = ("Left table " + osLeftTableName +
2208
0
                         " is not an existing layer in the dataset")
2209
0
                            .c_str();
2210
0
        return false;
2211
0
    }
2212
0
    const std::string &osRightTableName = poRelationship->GetRightTableName();
2213
0
    OGRGeoPackageLayer *poRightTable = cpl::down_cast<OGRGeoPackageLayer *>(
2214
0
        GetLayerByName(osRightTableName.c_str()));
2215
0
    if (!poRightTable)
2216
0
    {
2217
0
        failureReason = ("Right table " + osRightTableName +
2218
0
                         " is not an existing layer in the dataset")
2219
0
                            .c_str();
2220
0
        return false;
2221
0
    }
2222
2223
0
    const auto &aosLeftTableFields = poRelationship->GetLeftTableFields();
2224
0
    if (aosLeftTableFields.empty())
2225
0
    {
2226
0
        failureReason = "No left table fields were specified";
2227
0
        return false;
2228
0
    }
2229
0
    else if (aosLeftTableFields.size() > 1)
2230
0
    {
2231
0
        failureReason = "Only a single left table field is permitted for the "
2232
0
                        "GeoPackage specification";
2233
0
        return false;
2234
0
    }
2235
0
    else
2236
0
    {
2237
        // validate left field exists
2238
0
        if (poLeftTable->GetLayerDefn()->GetFieldIndex(
2239
0
                aosLeftTableFields[0].c_str()) < 0 &&
2240
0
            !EQUAL(poLeftTable->GetFIDColumn(), aosLeftTableFields[0].c_str()))
2241
0
        {
2242
0
            failureReason = ("Left table field " + aosLeftTableFields[0] +
2243
0
                             " does not exist in " + osLeftTableName)
2244
0
                                .c_str();
2245
0
            return false;
2246
0
        }
2247
0
    }
2248
2249
0
    const auto &aosRightTableFields = poRelationship->GetRightTableFields();
2250
0
    if (aosRightTableFields.empty())
2251
0
    {
2252
0
        failureReason = "No right table fields were specified";
2253
0
        return false;
2254
0
    }
2255
0
    else if (aosRightTableFields.size() > 1)
2256
0
    {
2257
0
        failureReason = "Only a single right table field is permitted for the "
2258
0
                        "GeoPackage specification";
2259
0
        return false;
2260
0
    }
2261
0
    else
2262
0
    {
2263
        // validate right field exists
2264
0
        if (poRightTable->GetLayerDefn()->GetFieldIndex(
2265
0
                aosRightTableFields[0].c_str()) < 0 &&
2266
0
            !EQUAL(poRightTable->GetFIDColumn(),
2267
0
                   aosRightTableFields[0].c_str()))
2268
0
        {
2269
0
            failureReason = ("Right table field " + aosRightTableFields[0] +
2270
0
                             " does not exist in " + osRightTableName)
2271
0
                                .c_str();
2272
0
            return false;
2273
0
        }
2274
0
    }
2275
2276
0
    return true;
2277
0
}
2278
2279
/************************************************************************/
2280
/*                         InitRaster()                                 */
2281
/************************************************************************/
2282
2283
bool GDALGeoPackageDataset::InitRaster(
2284
    GDALGeoPackageDataset *poParentDS, const char *pszTableName, double dfMinX,
2285
    double dfMinY, double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2286
    const char *pszContentsMinY, const char *pszContentsMaxX,
2287
    const char *pszContentsMaxY, char **papszOpenOptionsIn,
2288
    const SQLResult &oResult, int nIdxInResult)
2289
21
{
2290
21
    m_osRasterTable = pszTableName;
2291
21
    m_dfTMSMinX = dfMinX;
2292
21
    m_dfTMSMaxY = dfMaxY;
2293
2294
    // Despite prior checking, the type might be Binary and
2295
    // SQLResultGetValue() not working properly on it
2296
21
    int nZoomLevel = atoi(oResult.GetValue(0, nIdxInResult));
2297
21
    if (nZoomLevel < 0 || nZoomLevel > 65536)
2298
0
    {
2299
0
        return false;
2300
0
    }
2301
21
    double dfPixelXSize = CPLAtof(oResult.GetValue(1, nIdxInResult));
2302
21
    double dfPixelYSize = CPLAtof(oResult.GetValue(2, nIdxInResult));
2303
21
    if (dfPixelXSize <= 0 || dfPixelYSize <= 0)
2304
0
    {
2305
0
        return false;
2306
0
    }
2307
21
    int nTileWidth = atoi(oResult.GetValue(3, nIdxInResult));
2308
21
    int nTileHeight = atoi(oResult.GetValue(4, nIdxInResult));
2309
21
    if (nTileWidth <= 0 || nTileWidth > 65536 || nTileHeight <= 0 ||
2310
21
        nTileHeight > 65536)
2311
0
    {
2312
0
        return false;
2313
0
    }
2314
21
    int nTileMatrixWidth = static_cast<int>(
2315
21
        std::min(static_cast<GIntBig>(INT_MAX),
2316
21
                 CPLAtoGIntBig(oResult.GetValue(5, nIdxInResult))));
2317
21
    int nTileMatrixHeight = static_cast<int>(
2318
21
        std::min(static_cast<GIntBig>(INT_MAX),
2319
21
                 CPLAtoGIntBig(oResult.GetValue(6, nIdxInResult))));
2320
21
    if (nTileMatrixWidth <= 0 || nTileMatrixHeight <= 0)
2321
0
    {
2322
0
        return false;
2323
0
    }
2324
2325
    /* Use content bounds in priority over tile_matrix_set bounds */
2326
21
    double dfGDALMinX = dfMinX;
2327
21
    double dfGDALMinY = dfMinY;
2328
21
    double dfGDALMaxX = dfMaxX;
2329
21
    double dfGDALMaxY = dfMaxY;
2330
21
    pszContentsMinX =
2331
21
        CSLFetchNameValueDef(papszOpenOptionsIn, "MINX", pszContentsMinX);
2332
21
    pszContentsMinY =
2333
21
        CSLFetchNameValueDef(papszOpenOptionsIn, "MINY", pszContentsMinY);
2334
21
    pszContentsMaxX =
2335
21
        CSLFetchNameValueDef(papszOpenOptionsIn, "MAXX", pszContentsMaxX);
2336
21
    pszContentsMaxY =
2337
21
        CSLFetchNameValueDef(papszOpenOptionsIn, "MAXY", pszContentsMaxY);
2338
21
    if (pszContentsMinX != nullptr && pszContentsMinY != nullptr &&
2339
20
        pszContentsMaxX != nullptr && pszContentsMaxY != nullptr)
2340
20
    {
2341
20
        if (CPLAtof(pszContentsMinX) < CPLAtof(pszContentsMaxX) &&
2342
20
            CPLAtof(pszContentsMinY) < CPLAtof(pszContentsMaxY))
2343
19
        {
2344
19
            dfGDALMinX = CPLAtof(pszContentsMinX);
2345
19
            dfGDALMinY = CPLAtof(pszContentsMinY);
2346
19
            dfGDALMaxX = CPLAtof(pszContentsMaxX);
2347
19
            dfGDALMaxY = CPLAtof(pszContentsMaxY);
2348
19
        }
2349
1
        else
2350
1
        {
2351
1
            CPLError(CE_Warning, CPLE_AppDefined,
2352
1
                     "Illegal min_x/min_y/max_x/max_y values for %s in open "
2353
1
                     "options and/or gpkg_contents. Using bounds of "
2354
1
                     "gpkg_tile_matrix_set instead",
2355
1
                     pszTableName);
2356
1
        }
2357
20
    }
2358
21
    if (dfGDALMinX >= dfGDALMaxX || dfGDALMinY >= dfGDALMaxY)
2359
0
    {
2360
0
        CPLError(CE_Failure, CPLE_AppDefined,
2361
0
                 "Illegal min_x/min_y/max_x/max_y values for %s", pszTableName);
2362
0
        return false;
2363
0
    }
2364
2365
21
    int nBandCount = 0;
2366
21
    const char *pszBAND_COUNT =
2367
21
        CSLFetchNameValue(papszOpenOptionsIn, "BAND_COUNT");
2368
21
    if (poParentDS)
2369
1
    {
2370
1
        nBandCount = poParentDS->GetRasterCount();
2371
1
    }
2372
20
    else if (m_eDT != GDT_UInt8)
2373
4
    {
2374
4
        if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO") &&
2375
0
            !EQUAL(pszBAND_COUNT, "1"))
2376
0
        {
2377
0
            CPLError(CE_Warning, CPLE_AppDefined,
2378
0
                     "BAND_COUNT ignored for non-Byte data");
2379
0
        }
2380
4
        nBandCount = 1;
2381
4
    }
2382
16
    else
2383
16
    {
2384
16
        if (pszBAND_COUNT != nullptr && !EQUAL(pszBAND_COUNT, "AUTO"))
2385
0
        {
2386
0
            nBandCount = atoi(pszBAND_COUNT);
2387
0
            if (nBandCount == 1)
2388
0
                GetMetadata("IMAGE_STRUCTURE");
2389
0
        }
2390
16
        else
2391
16
        {
2392
16
            GetMetadata("IMAGE_STRUCTURE");
2393
16
            nBandCount = m_nBandCountFromMetadata;
2394
16
            if (nBandCount == 1)
2395
0
                m_eTF = GPKG_TF_PNG;
2396
16
        }
2397
16
        if (nBandCount == 1 && !m_osTFFromMetadata.empty())
2398
0
        {
2399
0
            m_eTF = GDALGPKGMBTilesGetTileFormat(m_osTFFromMetadata.c_str());
2400
0
        }
2401
16
        if (nBandCount <= 0 || nBandCount > 4)
2402
16
            nBandCount = 4;
2403
16
    }
2404
2405
21
    return InitRaster(poParentDS, pszTableName, nZoomLevel, nBandCount, dfMinX,
2406
21
                      dfMaxY, dfPixelXSize, dfPixelYSize, nTileWidth,
2407
21
                      nTileHeight, nTileMatrixWidth, nTileMatrixHeight,
2408
21
                      dfGDALMinX, dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
2409
21
}
2410
2411
/************************************************************************/
2412
/*                      ComputeTileAndPixelShifts()                     */
2413
/************************************************************************/
2414
2415
bool GDALGeoPackageDataset::ComputeTileAndPixelShifts()
2416
21
{
2417
21
    int nTileWidth, nTileHeight;
2418
21
    GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2419
2420
    // Compute shift between GDAL origin and TileMatrixSet origin
2421
21
    const double dfShiftXPixels = (m_gt[0] - m_dfTMSMinX) / m_gt[1];
2422
21
    if (!(dfShiftXPixels / nTileWidth >= INT_MIN &&
2423
21
          dfShiftXPixels / nTileWidth < INT_MAX))
2424
0
    {
2425
0
        return false;
2426
0
    }
2427
21
    const int64_t nShiftXPixels =
2428
21
        static_cast<int64_t>(floor(0.5 + dfShiftXPixels));
2429
21
    m_nShiftXTiles = static_cast<int>(nShiftXPixels / nTileWidth);
2430
21
    if (nShiftXPixels < 0 && (nShiftXPixels % nTileWidth) != 0)
2431
0
        m_nShiftXTiles--;
2432
21
    m_nShiftXPixelsMod =
2433
21
        (static_cast<int>(nShiftXPixels % nTileWidth) + nTileWidth) %
2434
21
        nTileWidth;
2435
2436
21
    const double dfShiftYPixels = (m_gt[3] - m_dfTMSMaxY) / m_gt[5];
2437
21
    if (!(dfShiftYPixels / nTileHeight >= INT_MIN &&
2438
21
          dfShiftYPixels / nTileHeight < INT_MAX))
2439
0
    {
2440
0
        return false;
2441
0
    }
2442
21
    const int64_t nShiftYPixels =
2443
21
        static_cast<int64_t>(floor(0.5 + dfShiftYPixels));
2444
21
    m_nShiftYTiles = static_cast<int>(nShiftYPixels / nTileHeight);
2445
21
    if (nShiftYPixels < 0 && (nShiftYPixels % nTileHeight) != 0)
2446
1
        m_nShiftYTiles--;
2447
21
    m_nShiftYPixelsMod =
2448
21
        (static_cast<int>(nShiftYPixels % nTileHeight) + nTileHeight) %
2449
21
        nTileHeight;
2450
21
    return true;
2451
21
}
2452
2453
/************************************************************************/
2454
/*                            AllocCachedTiles()                        */
2455
/************************************************************************/
2456
2457
bool GDALGeoPackageDataset::AllocCachedTiles()
2458
21
{
2459
21
    int nTileWidth, nTileHeight;
2460
21
    GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
2461
2462
    // We currently need 4 caches because of
2463
    // GDALGPKGMBTilesLikePseudoDataset::ReadTile(int nRow, int nCol)
2464
21
    const int nCacheCount = 4;
2465
    /*
2466
            (m_nShiftXPixelsMod != 0 || m_nShiftYPixelsMod != 0) ? 4 :
2467
            (GetUpdate() && m_eDT == GDT_UInt8) ? 2 : 1;
2468
    */
2469
21
    m_pabyCachedTiles = static_cast<GByte *>(VSI_MALLOC3_VERBOSE(
2470
21
        cpl::fits_on<int>(nCacheCount * (m_eDT == GDT_UInt8 ? 4 : 1) *
2471
21
                          m_nDTSize),
2472
21
        nTileWidth, nTileHeight));
2473
21
    if (m_pabyCachedTiles == nullptr)
2474
0
    {
2475
0
        CPLError(CE_Failure, CPLE_AppDefined, "Too big tiles: %d x %d",
2476
0
                 nTileWidth, nTileHeight);
2477
0
        return false;
2478
0
    }
2479
2480
21
    return true;
2481
21
}
2482
2483
/************************************************************************/
2484
/*                         InitRaster()                                 */
2485
/************************************************************************/
2486
2487
bool GDALGeoPackageDataset::InitRaster(
2488
    GDALGeoPackageDataset *poParentDS, const char *pszTableName, int nZoomLevel,
2489
    int nBandCount, double dfTMSMinX, double dfTMSMaxY, double dfPixelXSize,
2490
    double dfPixelYSize, int nTileWidth, int nTileHeight, int nTileMatrixWidth,
2491
    int nTileMatrixHeight, double dfGDALMinX, double dfGDALMinY,
2492
    double dfGDALMaxX, double dfGDALMaxY)
2493
21
{
2494
21
    m_osRasterTable = pszTableName;
2495
21
    m_dfTMSMinX = dfTMSMinX;
2496
21
    m_dfTMSMaxY = dfTMSMaxY;
2497
21
    m_nZoomLevel = nZoomLevel;
2498
21
    m_nTileMatrixWidth = nTileMatrixWidth;
2499
21
    m_nTileMatrixHeight = nTileMatrixHeight;
2500
2501
21
    m_bGeoTransformValid = true;
2502
21
    m_gt[0] = dfGDALMinX;
2503
21
    m_gt[1] = dfPixelXSize;
2504
21
    m_gt[3] = dfGDALMaxY;
2505
21
    m_gt[5] = -dfPixelYSize;
2506
21
    double dfRasterXSize = 0.5 + (dfGDALMaxX - dfGDALMinX) / dfPixelXSize;
2507
21
    double dfRasterYSize = 0.5 + (dfGDALMaxY - dfGDALMinY) / dfPixelYSize;
2508
21
    if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
2509
0
    {
2510
0
        CPLError(CE_Failure, CPLE_NotSupported, "Too big raster: %f x %f",
2511
0
                 dfRasterXSize, dfRasterYSize);
2512
0
        return false;
2513
0
    }
2514
21
    nRasterXSize = std::max(1, static_cast<int>(dfRasterXSize));
2515
21
    nRasterYSize = std::max(1, static_cast<int>(dfRasterYSize));
2516
2517
21
    if (poParentDS)
2518
1
    {
2519
1
        m_poParentDS = poParentDS;
2520
1
        eAccess = poParentDS->eAccess;
2521
1
        hDB = poParentDS->hDB;
2522
1
        m_eTF = poParentDS->m_eTF;
2523
1
        m_eDT = poParentDS->m_eDT;
2524
1
        m_nDTSize = poParentDS->m_nDTSize;
2525
1
        m_dfScale = poParentDS->m_dfScale;
2526
1
        m_dfOffset = poParentDS->m_dfOffset;
2527
1
        m_dfPrecision = poParentDS->m_dfPrecision;
2528
1
        m_usGPKGNull = poParentDS->m_usGPKGNull;
2529
1
        m_nQuality = poParentDS->m_nQuality;
2530
1
        m_nZLevel = poParentDS->m_nZLevel;
2531
1
        m_bDither = poParentDS->m_bDither;
2532
        /*m_nSRID = poParentDS->m_nSRID;*/
2533
1
        m_osWHERE = poParentDS->m_osWHERE;
2534
1
        SetDescription(CPLSPrintf("%s - zoom_level=%d",
2535
1
                                  poParentDS->GetDescription(), m_nZoomLevel));
2536
1
    }
2537
2538
93
    for (int i = 1; i <= nBandCount; i++)
2539
72
    {
2540
72
        auto poNewBand = std::make_unique<GDALGeoPackageRasterBand>(
2541
72
            this, nTileWidth, nTileHeight);
2542
72
        if (poParentDS)
2543
4
        {
2544
4
            int bHasNoData = FALSE;
2545
4
            double dfNoDataValue =
2546
4
                poParentDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
2547
4
            if (bHasNoData)
2548
0
                poNewBand->SetNoDataValueInternal(dfNoDataValue);
2549
4
        }
2550
2551
72
        if (nBandCount == 1 && m_poCTFromMetadata)
2552
0
        {
2553
0
            poNewBand->AssignColorTable(m_poCTFromMetadata.get());
2554
0
        }
2555
72
        if (!m_osNodataValueFromMetadata.empty())
2556
0
        {
2557
0
            poNewBand->SetNoDataValueInternal(
2558
0
                CPLAtof(m_osNodataValueFromMetadata.c_str()));
2559
0
        }
2560
2561
72
        SetBand(i, std::move(poNewBand));
2562
72
    }
2563
2564
21
    if (!ComputeTileAndPixelShifts())
2565
0
    {
2566
0
        CPLError(CE_Failure, CPLE_AppDefined,
2567
0
                 "Overflow occurred in ComputeTileAndPixelShifts()");
2568
0
        return false;
2569
0
    }
2570
2571
21
    GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
2572
21
    GDALPamDataset::SetMetadataItem("ZOOM_LEVEL",
2573
21
                                    CPLSPrintf("%d", m_nZoomLevel));
2574
2575
21
    return AllocCachedTiles();
2576
21
}
2577
2578
/************************************************************************/
2579
/*                 GDALGPKGMBTilesGetTileFormat()                       */
2580
/************************************************************************/
2581
2582
GPKGTileFormat GDALGPKGMBTilesGetTileFormat(const char *pszTF)
2583
0
{
2584
0
    GPKGTileFormat eTF = GPKG_TF_PNG_JPEG;
2585
0
    if (pszTF)
2586
0
    {
2587
0
        if (EQUAL(pszTF, "PNG_JPEG") || EQUAL(pszTF, "AUTO"))
2588
0
            eTF = GPKG_TF_PNG_JPEG;
2589
0
        else if (EQUAL(pszTF, "PNG"))
2590
0
            eTF = GPKG_TF_PNG;
2591
0
        else if (EQUAL(pszTF, "PNG8"))
2592
0
            eTF = GPKG_TF_PNG8;
2593
0
        else if (EQUAL(pszTF, "JPEG"))
2594
0
            eTF = GPKG_TF_JPEG;
2595
0
        else if (EQUAL(pszTF, "WEBP"))
2596
0
            eTF = GPKG_TF_WEBP;
2597
0
        else
2598
0
        {
2599
0
            CPLError(CE_Failure, CPLE_NotSupported,
2600
0
                     "Unsuppoted value for TILE_FORMAT: %s", pszTF);
2601
0
        }
2602
0
    }
2603
0
    return eTF;
2604
0
}
2605
2606
const char *GDALMBTilesGetTileFormatName(GPKGTileFormat eTF)
2607
0
{
2608
0
    switch (eTF)
2609
0
    {
2610
0
        case GPKG_TF_PNG:
2611
0
        case GPKG_TF_PNG8:
2612
0
            return "png";
2613
0
        case GPKG_TF_JPEG:
2614
0
            return "jpg";
2615
0
        case GPKG_TF_WEBP:
2616
0
            return "webp";
2617
0
        default:
2618
0
            break;
2619
0
    }
2620
0
    CPLError(CE_Failure, CPLE_NotSupported,
2621
0
             "Unsuppoted value for TILE_FORMAT: %d", static_cast<int>(eTF));
2622
0
    return nullptr;
2623
0
}
2624
2625
/************************************************************************/
2626
/*                         OpenRaster()                                 */
2627
/************************************************************************/
2628
2629
bool GDALGeoPackageDataset::OpenRaster(
2630
    const char *pszTableName, const char *pszIdentifier,
2631
    const char *pszDescription, int nSRSId, double dfMinX, double dfMinY,
2632
    double dfMaxX, double dfMaxY, const char *pszContentsMinX,
2633
    const char *pszContentsMinY, const char *pszContentsMaxX,
2634
    const char *pszContentsMaxY, bool bIsTiles, char **papszOpenOptionsIn)
2635
25
{
2636
25
    if (dfMinX >= dfMaxX || dfMinY >= dfMaxY)
2637
1
        return false;
2638
2639
    // Config option just for debug, and for example force set to NaN
2640
    // which is not supported
2641
24
    CPLString osDataNull = CPLGetConfigOption("GPKG_NODATA", "");
2642
24
    CPLString osUom;
2643
24
    CPLString osFieldName;
2644
24
    CPLString osGridCellEncoding;
2645
24
    if (!bIsTiles)
2646
4
    {
2647
4
        char *pszSQL = sqlite3_mprintf(
2648
4
            "SELECT datatype, scale, offset, data_null, precision FROM "
2649
4
            "gpkg_2d_gridded_coverage_ancillary "
2650
4
            "WHERE tile_matrix_set_name = '%q' "
2651
4
            "AND datatype IN ('integer', 'float')"
2652
4
            "AND (scale > 0 OR scale IS NULL)",
2653
4
            pszTableName);
2654
4
        auto oResult = SQLQuery(hDB, pszSQL);
2655
4
        sqlite3_free(pszSQL);
2656
4
        if (!oResult || oResult->RowCount() == 0)
2657
0
        {
2658
0
            return false;
2659
0
        }
2660
4
        const char *pszDataType = oResult->GetValue(0, 0);
2661
4
        const char *pszScale = oResult->GetValue(1, 0);
2662
4
        const char *pszOffset = oResult->GetValue(2, 0);
2663
4
        const char *pszDataNull = oResult->GetValue(3, 0);
2664
4
        const char *pszPrecision = oResult->GetValue(4, 0);
2665
4
        if (pszDataNull)
2666
0
            osDataNull = pszDataNull;
2667
4
        if (EQUAL(pszDataType, "float"))
2668
0
        {
2669
0
            SetDataType(GDT_Float32);
2670
0
            m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
2671
0
        }
2672
4
        else
2673
4
        {
2674
4
            SetDataType(GDT_Float32);
2675
4
            m_eTF = GPKG_TF_PNG_16BIT;
2676
4
            const double dfScale = pszScale ? CPLAtof(pszScale) : 1.0;
2677
4
            const double dfOffset = pszOffset ? CPLAtof(pszOffset) : 0.0;
2678
4
            if (dfScale == 1.0)
2679
4
            {
2680
4
                if (dfOffset == 0.0)
2681
4
                {
2682
4
                    SetDataType(GDT_UInt16);
2683
4
                }
2684
0
                else if (dfOffset == -32768.0)
2685
0
                {
2686
0
                    SetDataType(GDT_Int16);
2687
0
                }
2688
                // coverity[tainted_data]
2689
0
                else if (dfOffset == -32767.0 && !osDataNull.empty() &&
2690
0
                         CPLAtof(osDataNull) == 65535.0)
2691
                // Given that we will map the nodata value to -32768
2692
0
                {
2693
0
                    SetDataType(GDT_Int16);
2694
0
                }
2695
4
            }
2696
2697
            // Check that the tile offset and scales are compatible of a
2698
            // final integer result.
2699
4
            if (m_eDT != GDT_Float32)
2700
4
            {
2701
                // coverity[tainted_data]
2702
4
                if (dfScale == 1.0 && dfOffset == -32768.0 &&
2703
0
                    !osDataNull.empty() && CPLAtof(osDataNull) == 65535.0)
2704
0
                {
2705
                    // Given that we will map the nodata value to -32768
2706
0
                    pszSQL = sqlite3_mprintf(
2707
0
                        "SELECT 1 FROM "
2708
0
                        "gpkg_2d_gridded_tile_ancillary WHERE "
2709
0
                        "tpudt_name = '%q' "
2710
0
                        "AND NOT ((offset = 0.0 or offset = 1.0) "
2711
0
                        "AND scale = 1.0) "
2712
0
                        "LIMIT 1",
2713
0
                        pszTableName);
2714
0
                }
2715
4
                else
2716
4
                {
2717
4
                    pszSQL = sqlite3_mprintf(
2718
4
                        "SELECT 1 FROM "
2719
4
                        "gpkg_2d_gridded_tile_ancillary WHERE "
2720
4
                        "tpudt_name = '%q' "
2721
4
                        "AND NOT (offset = 0.0 AND scale = 1.0) LIMIT 1",
2722
4
                        pszTableName);
2723
4
                }
2724
4
                sqlite3_stmt *hSQLStmt = nullptr;
2725
4
                int rc =
2726
4
                    SQLPrepareWithError(hDB, pszSQL, -1, &hSQLStmt, nullptr);
2727
2728
4
                if (rc == SQLITE_OK)
2729
4
                {
2730
4
                    if (sqlite3_step(hSQLStmt) == SQLITE_ROW)
2731
0
                    {
2732
0
                        SetDataType(GDT_Float32);
2733
0
                    }
2734
4
                    sqlite3_finalize(hSQLStmt);
2735
4
                }
2736
4
                sqlite3_free(pszSQL);
2737
4
            }
2738
2739
4
            SetGlobalOffsetScale(dfOffset, dfScale);
2740
4
        }
2741
4
        if (pszPrecision)
2742
4
            m_dfPrecision = CPLAtof(pszPrecision);
2743
2744
        // Request those columns in a separate query, so as to keep
2745
        // compatibility with pre OGC 17-066r1 databases
2746
4
        pszSQL =
2747
4
            sqlite3_mprintf("SELECT uom, field_name, grid_cell_encoding FROM "
2748
4
                            "gpkg_2d_gridded_coverage_ancillary "
2749
4
                            "WHERE tile_matrix_set_name = '%q'",
2750
4
                            pszTableName);
2751
4
        CPLPushErrorHandler(CPLQuietErrorHandler);
2752
4
        oResult = SQLQuery(hDB, pszSQL);
2753
4
        CPLPopErrorHandler();
2754
4
        sqlite3_free(pszSQL);
2755
4
        if (oResult && oResult->RowCount() == 1)
2756
0
        {
2757
0
            const char *pszUom = oResult->GetValue(0, 0);
2758
0
            if (pszUom)
2759
0
                osUom = pszUom;
2760
0
            const char *pszFieldName = oResult->GetValue(1, 0);
2761
0
            if (pszFieldName)
2762
0
                osFieldName = pszFieldName;
2763
0
            const char *pszGridCellEncoding = oResult->GetValue(2, 0);
2764
0
            if (pszGridCellEncoding)
2765
0
                osGridCellEncoding = pszGridCellEncoding;
2766
0
        }
2767
4
    }
2768
2769
24
    m_bRecordInsertedInGPKGContent = true;
2770
24
    m_nSRID = nSRSId;
2771
2772
24
    if (auto poSRS = GetSpatialRef(nSRSId))
2773
19
    {
2774
19
        m_oSRS = *(poSRS.get());
2775
19
    }
2776
2777
    /* Various sanity checks added in the SELECT */
2778
24
    char *pszQuotedTableName = sqlite3_mprintf("'%q'", pszTableName);
2779
24
    CPLString osQuotedTableName(pszQuotedTableName);
2780
24
    sqlite3_free(pszQuotedTableName);
2781
24
    char *pszSQL = sqlite3_mprintf(
2782
24
        "SELECT zoom_level, pixel_x_size, pixel_y_size, tile_width, "
2783
24
        "tile_height, matrix_width, matrix_height "
2784
24
        "FROM gpkg_tile_matrix tm "
2785
24
        "WHERE table_name = %s "
2786
        // INT_MAX would be the theoretical maximum value to avoid
2787
        // overflows, but that's already a insane value.
2788
24
        "AND zoom_level >= 0 AND zoom_level <= 65536 "
2789
24
        "AND pixel_x_size > 0 AND pixel_y_size > 0 "
2790
24
        "AND tile_width >= 1 AND tile_width <= 65536 "
2791
24
        "AND tile_height >= 1 AND tile_height <= 65536 "
2792
24
        "AND matrix_width >= 1 AND matrix_height >= 1",
2793
24
        osQuotedTableName.c_str());
2794
24
    CPLString osSQL(pszSQL);
2795
24
    const char *pszZoomLevel =
2796
24
        CSLFetchNameValue(papszOpenOptionsIn, "ZOOM_LEVEL");
2797
24
    if (pszZoomLevel)
2798
0
    {
2799
0
        if (GetUpdate())
2800
0
            osSQL += CPLSPrintf(" AND zoom_level <= %d", atoi(pszZoomLevel));
2801
0
        else
2802
0
        {
2803
0
            osSQL += CPLSPrintf(
2804
0
                " AND (zoom_level = %d OR (zoom_level < %d AND EXISTS(SELECT 1 "
2805
0
                "FROM %s WHERE zoom_level = tm.zoom_level LIMIT 1)))",
2806
0
                atoi(pszZoomLevel), atoi(pszZoomLevel),
2807
0
                osQuotedTableName.c_str());
2808
0
        }
2809
0
    }
2810
    // In read-only mode, only lists non empty zoom levels
2811
24
    else if (!GetUpdate())
2812
24
    {
2813
24
        osSQL += CPLSPrintf(" AND EXISTS(SELECT 1 FROM %s WHERE zoom_level = "
2814
24
                            "tm.zoom_level LIMIT 1)",
2815
24
                            osQuotedTableName.c_str());
2816
24
    }
2817
0
    else  // if( pszZoomLevel == nullptr )
2818
0
    {
2819
0
        osSQL +=
2820
0
            CPLSPrintf(" AND zoom_level <= (SELECT MAX(zoom_level) FROM %s)",
2821
0
                       osQuotedTableName.c_str());
2822
0
    }
2823
24
    osSQL += " ORDER BY zoom_level DESC";
2824
    // To avoid denial of service.
2825
24
    osSQL += " LIMIT 100";
2826
2827
24
    auto oResult = SQLQuery(hDB, osSQL.c_str());
2828
24
    if (!oResult || oResult->RowCount() == 0)
2829
11
    {
2830
11
        if (oResult && oResult->RowCount() == 0 && pszContentsMinX != nullptr &&
2831
7
            pszContentsMinY != nullptr && pszContentsMaxX != nullptr &&
2832
7
            pszContentsMaxY != nullptr)
2833
7
        {
2834
7
            osSQL = pszSQL;
2835
7
            osSQL += " ORDER BY zoom_level DESC";
2836
7
            if (!GetUpdate())
2837
7
                osSQL += " LIMIT 1";
2838
7
            oResult = SQLQuery(hDB, osSQL.c_str());
2839
7
        }
2840
11
        if (!oResult || oResult->RowCount() == 0)
2841
4
        {
2842
4
            if (oResult && pszZoomLevel != nullptr)
2843
0
            {
2844
0
                CPLError(CE_Failure, CPLE_AppDefined,
2845
0
                         "ZOOM_LEVEL is probably not valid w.r.t tile "
2846
0
                         "table content");
2847
0
            }
2848
4
            sqlite3_free(pszSQL);
2849
4
            return false;
2850
4
        }
2851
11
    }
2852
20
    sqlite3_free(pszSQL);
2853
2854
    // If USE_TILE_EXTENT=YES, then query the tile table to find which tiles
2855
    // actually exist.
2856
2857
    // CAUTION: Do not move those variables inside inner scope !
2858
20
    CPLString osContentsMinX, osContentsMinY, osContentsMaxX, osContentsMaxY;
2859
2860
20
    if (CPLTestBool(
2861
20
            CSLFetchNameValueDef(papszOpenOptionsIn, "USE_TILE_EXTENT", "NO")))
2862
0
    {
2863
0
        pszSQL = sqlite3_mprintf(
2864
0
            "SELECT MIN(tile_column), MIN(tile_row), MAX(tile_column), "
2865
0
            "MAX(tile_row) FROM \"%w\" WHERE zoom_level = %d",
2866
0
            pszTableName, atoi(oResult->GetValue(0, 0)));
2867
0
        auto oResult2 = SQLQuery(hDB, pszSQL);
2868
0
        sqlite3_free(pszSQL);
2869
0
        if (!oResult2 || oResult2->RowCount() == 0 ||
2870
            // Can happen if table is empty
2871
0
            oResult2->GetValue(0, 0) == nullptr ||
2872
            // Can happen if table has no NOT NULL constraint on tile_row
2873
            // and that all tile_row are NULL
2874
0
            oResult2->GetValue(1, 0) == nullptr)
2875
0
        {
2876
0
            return false;
2877
0
        }
2878
0
        const double dfPixelXSize = CPLAtof(oResult->GetValue(1, 0));
2879
0
        const double dfPixelYSize = CPLAtof(oResult->GetValue(2, 0));
2880
0
        const int nTileWidth = atoi(oResult->GetValue(3, 0));
2881
0
        const int nTileHeight = atoi(oResult->GetValue(4, 0));
2882
0
        osContentsMinX =
2883
0
            CPLSPrintf("%.17g", dfMinX + dfPixelXSize * nTileWidth *
2884
0
                                             atoi(oResult2->GetValue(0, 0)));
2885
0
        osContentsMaxY =
2886
0
            CPLSPrintf("%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2887
0
                                             atoi(oResult2->GetValue(1, 0)));
2888
0
        osContentsMaxX = CPLSPrintf(
2889
0
            "%.17g", dfMinX + dfPixelXSize * nTileWidth *
2890
0
                                  (1 + atoi(oResult2->GetValue(2, 0))));
2891
0
        osContentsMinY = CPLSPrintf(
2892
0
            "%.17g", dfMaxY - dfPixelYSize * nTileHeight *
2893
0
                                  (1 + atoi(oResult2->GetValue(3, 0))));
2894
0
        pszContentsMinX = osContentsMinX.c_str();
2895
0
        pszContentsMinY = osContentsMinY.c_str();
2896
0
        pszContentsMaxX = osContentsMaxX.c_str();
2897
0
        pszContentsMaxY = osContentsMaxY.c_str();
2898
0
    }
2899
2900
20
    if (!InitRaster(nullptr, pszTableName, dfMinX, dfMinY, dfMaxX, dfMaxY,
2901
20
                    pszContentsMinX, pszContentsMinY, pszContentsMaxX,
2902
20
                    pszContentsMaxY, papszOpenOptionsIn, *oResult, 0))
2903
0
    {
2904
0
        return false;
2905
0
    }
2906
2907
20
    auto poBand = cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(1));
2908
20
    if (!osDataNull.empty())
2909
0
    {
2910
0
        double dfGPKGNoDataValue = CPLAtof(osDataNull);
2911
0
        if (m_eTF == GPKG_TF_PNG_16BIT)
2912
0
        {
2913
0
            if (dfGPKGNoDataValue < 0 || dfGPKGNoDataValue > 65535 ||
2914
0
                static_cast<int>(dfGPKGNoDataValue) != dfGPKGNoDataValue)
2915
0
            {
2916
0
                CPLError(CE_Warning, CPLE_AppDefined,
2917
0
                         "data_null = %.17g is invalid for integer data_type",
2918
0
                         dfGPKGNoDataValue);
2919
0
            }
2920
0
            else
2921
0
            {
2922
0
                m_usGPKGNull = static_cast<GUInt16>(dfGPKGNoDataValue);
2923
0
                if (m_eDT == GDT_Int16 && m_usGPKGNull > 32767)
2924
0
                    dfGPKGNoDataValue = -32768.0;
2925
0
                else if (m_eDT == GDT_Float32)
2926
0
                {
2927
                    // Pick a value that is unlikely to be hit with offset &
2928
                    // scale
2929
0
                    dfGPKGNoDataValue = -std::numeric_limits<float>::max();
2930
0
                }
2931
0
                poBand->SetNoDataValueInternal(dfGPKGNoDataValue);
2932
0
            }
2933
0
        }
2934
0
        else
2935
0
        {
2936
0
            poBand->SetNoDataValueInternal(
2937
0
                static_cast<float>(dfGPKGNoDataValue));
2938
0
        }
2939
0
    }
2940
20
    if (!osUom.empty())
2941
0
    {
2942
0
        poBand->SetUnitTypeInternal(osUom);
2943
0
    }
2944
20
    if (!osFieldName.empty())
2945
0
    {
2946
0
        GetRasterBand(1)->GDALRasterBand::SetDescription(osFieldName);
2947
0
    }
2948
20
    if (!osGridCellEncoding.empty())
2949
0
    {
2950
0
        if (osGridCellEncoding == "grid-value-is-center")
2951
0
        {
2952
0
            GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2953
0
                                            GDALMD_AOP_POINT);
2954
0
        }
2955
0
        else if (osGridCellEncoding == "grid-value-is-area")
2956
0
        {
2957
0
            GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2958
0
                                            GDALMD_AOP_AREA);
2959
0
        }
2960
0
        else
2961
0
        {
2962
0
            GDALPamDataset::SetMetadataItem(GDALMD_AREA_OR_POINT,
2963
0
                                            GDALMD_AOP_POINT);
2964
0
            GetRasterBand(1)->GDALRasterBand::SetMetadataItem(
2965
0
                "GRID_CELL_ENCODING", osGridCellEncoding);
2966
0
        }
2967
0
    }
2968
2969
20
    CheckUnknownExtensions(true);
2970
2971
    // Do this after CheckUnknownExtensions() so that m_eTF is set to
2972
    // GPKG_TF_WEBP if the table already registers the gpkg_webp extension
2973
20
    const char *pszTF = CSLFetchNameValue(papszOpenOptionsIn, "TILE_FORMAT");
2974
20
    if (pszTF)
2975
0
    {
2976
0
        if (!GetUpdate())
2977
0
        {
2978
0
            CPLError(CE_Warning, CPLE_AppDefined,
2979
0
                     "TILE_FORMAT open option ignored in read-only mode");
2980
0
        }
2981
0
        else if (m_eTF == GPKG_TF_PNG_16BIT ||
2982
0
                 m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
2983
0
        {
2984
0
            CPLError(CE_Warning, CPLE_AppDefined,
2985
0
                     "TILE_FORMAT open option ignored on gridded coverages");
2986
0
        }
2987
0
        else
2988
0
        {
2989
0
            GPKGTileFormat eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
2990
0
            if (eTF == GPKG_TF_WEBP && m_eTF != eTF)
2991
0
            {
2992
0
                if (!RegisterWebPExtension())
2993
0
                    return false;
2994
0
            }
2995
0
            m_eTF = eTF;
2996
0
        }
2997
0
    }
2998
2999
20
    ParseCompressionOptions(papszOpenOptionsIn);
3000
3001
20
    m_osWHERE = CSLFetchNameValueDef(papszOpenOptionsIn, "WHERE", "");
3002
3003
    // Set metadata
3004
20
    if (pszIdentifier && pszIdentifier[0])
3005
20
        GDALPamDataset::SetMetadataItem("IDENTIFIER", pszIdentifier);
3006
20
    if (pszDescription && pszDescription[0])
3007
0
        GDALPamDataset::SetMetadataItem("DESCRIPTION", pszDescription);
3008
3009
    // Add overviews
3010
20
    for (int i = 1; i < oResult->RowCount(); i++)
3011
1
    {
3012
1
        auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3013
1
        poOvrDS->ShareLockWithParentDataset(this);
3014
1
        if (!poOvrDS->InitRaster(this, pszTableName, dfMinX, dfMinY, dfMaxX,
3015
1
                                 dfMaxY, pszContentsMinX, pszContentsMinY,
3016
1
                                 pszContentsMaxX, pszContentsMaxY,
3017
1
                                 papszOpenOptionsIn, *oResult, i))
3018
0
        {
3019
0
            break;
3020
0
        }
3021
3022
1
        int nTileWidth, nTileHeight;
3023
1
        poOvrDS->GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3024
1
        const bool bStop =
3025
1
            (eAccess == GA_ReadOnly && poOvrDS->GetRasterXSize() < nTileWidth &&
3026
1
             poOvrDS->GetRasterYSize() < nTileHeight);
3027
3028
1
        m_apoOverviewDS.push_back(std::move(poOvrDS));
3029
3030
1
        if (bStop)
3031
1
        {
3032
1
            break;
3033
1
        }
3034
1
    }
3035
3036
20
    return true;
3037
20
}
3038
3039
/************************************************************************/
3040
/*                           GetSpatialRef()                            */
3041
/************************************************************************/
3042
3043
const OGRSpatialReference *GDALGeoPackageDataset::GetSpatialRef() const
3044
24
{
3045
24
    if (GetLayerCount())
3046
0
        return GDALDataset::GetSpatialRef();
3047
24
    return GetSpatialRefRasterOnly();
3048
24
}
3049
3050
/************************************************************************/
3051
/*                      GetSpatialRefRasterOnly()                       */
3052
/************************************************************************/
3053
3054
const OGRSpatialReference *
3055
GDALGeoPackageDataset::GetSpatialRefRasterOnly() const
3056
3057
24
{
3058
24
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
3059
24
}
3060
3061
/************************************************************************/
3062
/*                           SetSpatialRef()                            */
3063
/************************************************************************/
3064
3065
CPLErr GDALGeoPackageDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
3066
0
{
3067
0
    if (nBands == 0)
3068
0
    {
3069
0
        CPLError(CE_Failure, CPLE_NotSupported,
3070
0
                 "SetProjection() not supported on a dataset with 0 band");
3071
0
        return CE_Failure;
3072
0
    }
3073
0
    if (eAccess != GA_Update)
3074
0
    {
3075
0
        CPLError(CE_Failure, CPLE_NotSupported,
3076
0
                 "SetProjection() not supported on read-only dataset");
3077
0
        return CE_Failure;
3078
0
    }
3079
3080
0
    const int nSRID = GetSrsId(poSRS);
3081
0
    const auto poTS = GetTilingScheme(m_osTilingScheme);
3082
0
    if (poTS && nSRID != poTS->nEPSGCode)
3083
0
    {
3084
0
        CPLError(CE_Failure, CPLE_NotSupported,
3085
0
                 "Projection should be EPSG:%d for %s tiling scheme",
3086
0
                 poTS->nEPSGCode, m_osTilingScheme.c_str());
3087
0
        return CE_Failure;
3088
0
    }
3089
3090
0
    m_nSRID = nSRID;
3091
0
    m_oSRS.Clear();
3092
0
    if (poSRS)
3093
0
        m_oSRS = *poSRS;
3094
3095
0
    if (m_bRecordInsertedInGPKGContent)
3096
0
    {
3097
0
        char *pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET srs_id = %d "
3098
0
                                       "WHERE lower(table_name) = lower('%q')",
3099
0
                                       m_nSRID, m_osRasterTable.c_str());
3100
0
        OGRErr eErr = SQLCommand(hDB, pszSQL);
3101
0
        sqlite3_free(pszSQL);
3102
0
        if (eErr != OGRERR_NONE)
3103
0
            return CE_Failure;
3104
3105
0
        pszSQL = sqlite3_mprintf("UPDATE gpkg_tile_matrix_set SET srs_id = %d "
3106
0
                                 "WHERE lower(table_name) = lower('%q')",
3107
0
                                 m_nSRID, m_osRasterTable.c_str());
3108
0
        eErr = SQLCommand(hDB, pszSQL);
3109
0
        sqlite3_free(pszSQL);
3110
0
        if (eErr != OGRERR_NONE)
3111
0
            return CE_Failure;
3112
0
    }
3113
3114
0
    return CE_None;
3115
0
}
3116
3117
/************************************************************************/
3118
/*                          GetGeoTransform()                           */
3119
/************************************************************************/
3120
3121
CPLErr GDALGeoPackageDataset::GetGeoTransform(GDALGeoTransform &gt) const
3122
24
{
3123
24
    gt = m_gt;
3124
24
    if (!m_bGeoTransformValid)
3125
4
        return CE_Failure;
3126
20
    else
3127
20
        return CE_None;
3128
24
}
3129
3130
/************************************************************************/
3131
/*                          SetGeoTransform()                           */
3132
/************************************************************************/
3133
3134
CPLErr GDALGeoPackageDataset::SetGeoTransform(const GDALGeoTransform &gt)
3135
0
{
3136
0
    if (nBands == 0)
3137
0
    {
3138
0
        CPLError(CE_Failure, CPLE_NotSupported,
3139
0
                 "SetGeoTransform() not supported on a dataset with 0 band");
3140
0
        return CE_Failure;
3141
0
    }
3142
0
    if (eAccess != GA_Update)
3143
0
    {
3144
0
        CPLError(CE_Failure, CPLE_NotSupported,
3145
0
                 "SetGeoTransform() not supported on read-only dataset");
3146
0
        return CE_Failure;
3147
0
    }
3148
0
    if (m_bGeoTransformValid)
3149
0
    {
3150
0
        CPLError(CE_Failure, CPLE_NotSupported,
3151
0
                 "Cannot modify geotransform once set");
3152
0
        return CE_Failure;
3153
0
    }
3154
0
    if (gt[2] != 0.0 || gt[4] != 0 || gt[5] > 0.0)
3155
0
    {
3156
0
        CPLError(CE_Failure, CPLE_NotSupported,
3157
0
                 "Only north-up non rotated geotransform supported");
3158
0
        return CE_Failure;
3159
0
    }
3160
3161
0
    if (m_nZoomLevel < 0)
3162
0
    {
3163
0
        const auto poTS = GetTilingScheme(m_osTilingScheme);
3164
0
        if (poTS)
3165
0
        {
3166
0
            double dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3167
0
            double dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3168
0
            for (m_nZoomLevel = 0; m_nZoomLevel < MAX_ZOOM_LEVEL;
3169
0
                 m_nZoomLevel++)
3170
0
            {
3171
0
                double dfExpectedPixelXSize =
3172
0
                    dfPixelXSizeZoomLevel0 / (1 << m_nZoomLevel);
3173
0
                double dfExpectedPixelYSize =
3174
0
                    dfPixelYSizeZoomLevel0 / (1 << m_nZoomLevel);
3175
0
                if (fabs(gt[1] - dfExpectedPixelXSize) <
3176
0
                        1e-8 * dfExpectedPixelXSize &&
3177
0
                    fabs(fabs(gt[5]) - dfExpectedPixelYSize) <
3178
0
                        1e-8 * dfExpectedPixelYSize)
3179
0
                {
3180
0
                    break;
3181
0
                }
3182
0
            }
3183
0
            if (m_nZoomLevel == MAX_ZOOM_LEVEL)
3184
0
            {
3185
0
                m_nZoomLevel = -1;
3186
0
                CPLError(
3187
0
                    CE_Failure, CPLE_NotSupported,
3188
0
                    "Could not find an appropriate zoom level of %s tiling "
3189
0
                    "scheme that matches raster pixel size",
3190
0
                    m_osTilingScheme.c_str());
3191
0
                return CE_Failure;
3192
0
            }
3193
0
        }
3194
0
    }
3195
3196
0
    m_gt = gt;
3197
0
    m_bGeoTransformValid = true;
3198
3199
0
    return FinalizeRasterRegistration();
3200
0
}
3201
3202
/************************************************************************/
3203
/*                      FinalizeRasterRegistration()                    */
3204
/************************************************************************/
3205
3206
CPLErr GDALGeoPackageDataset::FinalizeRasterRegistration()
3207
0
{
3208
0
    OGRErr eErr;
3209
3210
0
    m_dfTMSMinX = m_gt[0];
3211
0
    m_dfTMSMaxY = m_gt[3];
3212
3213
0
    int nTileWidth, nTileHeight;
3214
0
    GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3215
3216
0
    if (m_nZoomLevel < 0)
3217
0
    {
3218
0
        m_nZoomLevel = 0;
3219
0
        while ((nRasterXSize >> m_nZoomLevel) > nTileWidth ||
3220
0
               (nRasterYSize >> m_nZoomLevel) > nTileHeight)
3221
0
            m_nZoomLevel++;
3222
0
    }
3223
3224
0
    double dfPixelXSizeZoomLevel0 = m_gt[1] * (1 << m_nZoomLevel);
3225
0
    double dfPixelYSizeZoomLevel0 = fabs(m_gt[5]) * (1 << m_nZoomLevel);
3226
0
    int nTileXCountZoomLevel0 =
3227
0
        std::max(1, DIV_ROUND_UP((nRasterXSize >> m_nZoomLevel), nTileWidth));
3228
0
    int nTileYCountZoomLevel0 =
3229
0
        std::max(1, DIV_ROUND_UP((nRasterYSize >> m_nZoomLevel), nTileHeight));
3230
3231
0
    const auto poTS = GetTilingScheme(m_osTilingScheme);
3232
0
    if (poTS)
3233
0
    {
3234
0
        CPLAssert(m_nZoomLevel >= 0);
3235
0
        m_dfTMSMinX = poTS->dfMinX;
3236
0
        m_dfTMSMaxY = poTS->dfMaxY;
3237
0
        dfPixelXSizeZoomLevel0 = poTS->dfPixelXSizeZoomLevel0;
3238
0
        dfPixelYSizeZoomLevel0 = poTS->dfPixelYSizeZoomLevel0;
3239
0
        nTileXCountZoomLevel0 = poTS->nTileXCountZoomLevel0;
3240
0
        nTileYCountZoomLevel0 = poTS->nTileYCountZoomLevel0;
3241
0
    }
3242
0
    m_nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << m_nZoomLevel);
3243
0
    m_nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << m_nZoomLevel);
3244
3245
0
    if (!ComputeTileAndPixelShifts())
3246
0
    {
3247
0
        CPLError(CE_Failure, CPLE_AppDefined,
3248
0
                 "Overflow occurred in ComputeTileAndPixelShifts()");
3249
0
        return CE_Failure;
3250
0
    }
3251
3252
0
    if (!AllocCachedTiles())
3253
0
    {
3254
0
        return CE_Failure;
3255
0
    }
3256
3257
0
    double dfGDALMinX = m_gt[0];
3258
0
    double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3259
0
    double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3260
0
    double dfGDALMaxY = m_gt[3];
3261
3262
0
    if (SoftStartTransaction() != OGRERR_NONE)
3263
0
        return CE_Failure;
3264
3265
0
    const char *pszCurrentDate =
3266
0
        CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3267
0
    CPLString osInsertGpkgContentsFormatting(
3268
0
        "INSERT INTO gpkg_contents "
3269
0
        "(table_name,data_type,identifier,description,min_x,min_y,max_x,max_y,"
3270
0
        "last_change,srs_id) VALUES "
3271
0
        "('%q','%q','%q','%q',%.17g,%.17g,%.17g,%.17g,");
3272
0
    osInsertGpkgContentsFormatting += (pszCurrentDate) ? "'%q'" : "%s";
3273
0
    osInsertGpkgContentsFormatting += ",%d)";
3274
0
    char *pszSQL = sqlite3_mprintf(
3275
0
        osInsertGpkgContentsFormatting.c_str(), m_osRasterTable.c_str(),
3276
0
        (m_eDT == GDT_UInt8) ? "tiles" : "2d-gridded-coverage",
3277
0
        m_osIdentifier.c_str(), m_osDescription.c_str(), dfGDALMinX, dfGDALMinY,
3278
0
        dfGDALMaxX, dfGDALMaxY,
3279
0
        pszCurrentDate ? pszCurrentDate
3280
0
                       : "strftime('%Y-%m-%dT%H:%M:%fZ','now')",
3281
0
        m_nSRID);
3282
3283
0
    eErr = SQLCommand(hDB, pszSQL);
3284
0
    sqlite3_free(pszSQL);
3285
0
    if (eErr != OGRERR_NONE)
3286
0
    {
3287
0
        SoftRollbackTransaction();
3288
0
        return CE_Failure;
3289
0
    }
3290
3291
0
    double dfTMSMaxX = m_dfTMSMinX + nTileXCountZoomLevel0 * nTileWidth *
3292
0
                                         dfPixelXSizeZoomLevel0;
3293
0
    double dfTMSMinY = m_dfTMSMaxY - nTileYCountZoomLevel0 * nTileHeight *
3294
0
                                         dfPixelYSizeZoomLevel0;
3295
3296
0
    pszSQL =
3297
0
        sqlite3_mprintf("INSERT INTO gpkg_tile_matrix_set "
3298
0
                        "(table_name,srs_id,min_x,min_y,max_x,max_y) VALUES "
3299
0
                        "('%q',%d,%.17g,%.17g,%.17g,%.17g)",
3300
0
                        m_osRasterTable.c_str(), m_nSRID, m_dfTMSMinX,
3301
0
                        dfTMSMinY, dfTMSMaxX, m_dfTMSMaxY);
3302
0
    eErr = SQLCommand(hDB, pszSQL);
3303
0
    sqlite3_free(pszSQL);
3304
0
    if (eErr != OGRERR_NONE)
3305
0
    {
3306
0
        SoftRollbackTransaction();
3307
0
        return CE_Failure;
3308
0
    }
3309
3310
0
    m_apoOverviewDS.resize(m_nZoomLevel);
3311
3312
0
    for (int i = 0; i <= m_nZoomLevel; i++)
3313
0
    {
3314
0
        double dfPixelXSizeZoomLevel = 0.0;
3315
0
        double dfPixelYSizeZoomLevel = 0.0;
3316
0
        int nTileMatrixWidth = 0;
3317
0
        int nTileMatrixHeight = 0;
3318
0
        if (EQUAL(m_osTilingScheme, "CUSTOM"))
3319
0
        {
3320
0
            dfPixelXSizeZoomLevel = m_gt[1] * (1 << (m_nZoomLevel - i));
3321
0
            dfPixelYSizeZoomLevel = fabs(m_gt[5]) * (1 << (m_nZoomLevel - i));
3322
0
        }
3323
0
        else
3324
0
        {
3325
0
            dfPixelXSizeZoomLevel = dfPixelXSizeZoomLevel0 / (1 << i);
3326
0
            dfPixelYSizeZoomLevel = dfPixelYSizeZoomLevel0 / (1 << i);
3327
0
        }
3328
0
        nTileMatrixWidth = nTileXCountZoomLevel0 * (1 << i);
3329
0
        nTileMatrixHeight = nTileYCountZoomLevel0 * (1 << i);
3330
3331
0
        pszSQL = sqlite3_mprintf(
3332
0
            "INSERT INTO gpkg_tile_matrix "
3333
0
            "(table_name,zoom_level,matrix_width,matrix_height,tile_width,tile_"
3334
0
            "height,pixel_x_size,pixel_y_size) VALUES "
3335
0
            "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3336
0
            m_osRasterTable.c_str(), i, nTileMatrixWidth, nTileMatrixHeight,
3337
0
            nTileWidth, nTileHeight, dfPixelXSizeZoomLevel,
3338
0
            dfPixelYSizeZoomLevel);
3339
0
        eErr = SQLCommand(hDB, pszSQL);
3340
0
        sqlite3_free(pszSQL);
3341
0
        if (eErr != OGRERR_NONE)
3342
0
        {
3343
0
            SoftRollbackTransaction();
3344
0
            return CE_Failure;
3345
0
        }
3346
3347
0
        if (i < m_nZoomLevel)
3348
0
        {
3349
0
            auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3350
0
            poOvrDS->ShareLockWithParentDataset(this);
3351
0
            poOvrDS->InitRaster(this, m_osRasterTable, i, nBands, m_dfTMSMinX,
3352
0
                                m_dfTMSMaxY, dfPixelXSizeZoomLevel,
3353
0
                                dfPixelYSizeZoomLevel, nTileWidth, nTileHeight,
3354
0
                                nTileMatrixWidth, nTileMatrixHeight, dfGDALMinX,
3355
0
                                dfGDALMinY, dfGDALMaxX, dfGDALMaxY);
3356
3357
0
            m_apoOverviewDS[m_nZoomLevel - 1 - i] = std::move(poOvrDS);
3358
0
        }
3359
0
    }
3360
3361
0
    if (!m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.empty())
3362
0
    {
3363
0
        eErr = SQLCommand(
3364
0
            hDB, m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.c_str());
3365
0
        m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary.clear();
3366
0
        if (eErr != OGRERR_NONE)
3367
0
        {
3368
0
            SoftRollbackTransaction();
3369
0
            return CE_Failure;
3370
0
        }
3371
0
    }
3372
3373
0
    SoftCommitTransaction();
3374
3375
0
    m_apoOverviewDS.resize(m_nZoomLevel);
3376
0
    m_bRecordInsertedInGPKGContent = true;
3377
3378
0
    return CE_None;
3379
0
}
3380
3381
/************************************************************************/
3382
/*                             FlushCache()                             */
3383
/************************************************************************/
3384
3385
CPLErr GDALGeoPackageDataset::FlushCache(bool bAtClosing)
3386
2.99k
{
3387
2.99k
    if (m_bInFlushCache)
3388
0
        return CE_None;
3389
3390
2.99k
    if (eAccess == GA_Update || !m_bMetadataDirty)
3391
2.99k
    {
3392
2.99k
        SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3393
2.99k
    }
3394
3395
2.99k
    if (m_bRemoveOGREmptyTable)
3396
483
    {
3397
483
        m_bRemoveOGREmptyTable = false;
3398
483
        RemoveOGREmptyTable();
3399
483
    }
3400
3401
2.99k
    CPLErr eErr = IFlushCacheWithErrCode(bAtClosing);
3402
3403
2.99k
    FlushMetadata();
3404
3405
2.99k
    if (eAccess == GA_Update || !m_bMetadataDirty)
3406
2.99k
    {
3407
        // Needed again as above IFlushCacheWithErrCode()
3408
        // may have call GDALGeoPackageRasterBand::InvalidateStatistics()
3409
        // which modifies metadata
3410
2.99k
        SetPamFlags(GetPamFlags() & ~GPF_DIRTY);
3411
2.99k
    }
3412
3413
2.99k
    return eErr;
3414
2.99k
}
3415
3416
CPLErr GDALGeoPackageDataset::IFlushCacheWithErrCode(bool bAtClosing)
3417
3418
3.06k
{
3419
3.06k
    if (m_bInFlushCache)
3420
72
        return CE_None;
3421
2.99k
    m_bInFlushCache = true;
3422
2.99k
    if (hDB && eAccess == GA_ReadOnly && bAtClosing)
3423
1.90k
    {
3424
        // Clean-up metadata that will go to PAM by removing items that
3425
        // are reconstructed.
3426
1.90k
        CPLStringList aosMD;
3427
2.06k
        for (CSLConstList papszIter = GetMetadata(); papszIter && *papszIter;
3428
1.90k
             ++papszIter)
3429
168
        {
3430
168
            char *pszKey = nullptr;
3431
168
            CPLParseNameValue(*papszIter, &pszKey);
3432
168
            if (pszKey &&
3433
168
                (EQUAL(pszKey, "AREA_OR_POINT") ||
3434
168
                 EQUAL(pszKey, "IDENTIFIER") || EQUAL(pszKey, "DESCRIPTION") ||
3435
148
                 EQUAL(pszKey, "ZOOM_LEVEL") ||
3436
127
                 STARTS_WITH(pszKey, "GPKG_METADATA_ITEM_")))
3437
41
            {
3438
                // remove it
3439
41
            }
3440
127
            else
3441
127
            {
3442
127
                aosMD.AddString(*papszIter);
3443
127
            }
3444
168
            CPLFree(pszKey);
3445
168
        }
3446
1.90k
        oMDMD.SetMetadata(aosMD.List());
3447
1.90k
        oMDMD.SetMetadata(nullptr, "IMAGE_STRUCTURE");
3448
3449
1.90k
        GDALPamDataset::FlushCache(bAtClosing);
3450
1.90k
    }
3451
1.09k
    else
3452
1.09k
    {
3453
        // Short circuit GDALPamDataset to avoid serialization to .aux.xml
3454
1.09k
        GDALDataset::FlushCache(bAtClosing);
3455
1.09k
    }
3456
3457
2.99k
    for (auto &poLayer : m_apoLayers)
3458
10.0k
    {
3459
10.0k
        poLayer->RunDeferredCreationIfNecessary();
3460
10.0k
        poLayer->CreateSpatialIndexIfNecessary();
3461
10.0k
    }
3462
3463
    // Update raster table last_change column in gpkg_contents if needed
3464
2.99k
    if (m_bHasModifiedTiles)
3465
0
    {
3466
0
        for (int i = 1; i <= nBands; ++i)
3467
0
        {
3468
0
            auto poBand =
3469
0
                cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
3470
0
            if (!poBand->HaveStatsMetadataBeenSetInThisSession())
3471
0
            {
3472
0
                poBand->InvalidateStatistics();
3473
0
                if (psPam && psPam->pszPamFilename)
3474
0
                    VSIUnlink(psPam->pszPamFilename);
3475
0
            }
3476
0
        }
3477
3478
0
        UpdateGpkgContentsLastChange(m_osRasterTable);
3479
3480
0
        m_bHasModifiedTiles = false;
3481
0
    }
3482
3483
2.99k
    CPLErr eErr = FlushTiles();
3484
3485
2.99k
    m_bInFlushCache = false;
3486
2.99k
    return eErr;
3487
3.06k
}
3488
3489
/************************************************************************/
3490
/*                       GetCurrentDateEscapedSQL()                      */
3491
/************************************************************************/
3492
3493
std::string GDALGeoPackageDataset::GetCurrentDateEscapedSQL()
3494
6.60k
{
3495
6.60k
    const char *pszCurrentDate =
3496
6.60k
        CPLGetConfigOption("OGR_CURRENT_DATE", nullptr);
3497
6.60k
    if (pszCurrentDate)
3498
0
        return '\'' + SQLEscapeLiteral(pszCurrentDate) + '\'';
3499
6.60k
    return "strftime('%Y-%m-%dT%H:%M:%fZ','now')";
3500
6.60k
}
3501
3502
/************************************************************************/
3503
/*                    UpdateGpkgContentsLastChange()                    */
3504
/************************************************************************/
3505
3506
OGRErr
3507
GDALGeoPackageDataset::UpdateGpkgContentsLastChange(const char *pszTableName)
3508
2.94k
{
3509
2.94k
    char *pszSQL =
3510
2.94k
        sqlite3_mprintf("UPDATE gpkg_contents SET "
3511
2.94k
                        "last_change = %s "
3512
2.94k
                        "WHERE lower(table_name) = lower('%q')",
3513
2.94k
                        GetCurrentDateEscapedSQL().c_str(), pszTableName);
3514
2.94k
    OGRErr eErr = SQLCommand(hDB, pszSQL);
3515
2.94k
    sqlite3_free(pszSQL);
3516
2.94k
    return eErr;
3517
2.94k
}
3518
3519
/************************************************************************/
3520
/*                          IBuildOverviews()                           */
3521
/************************************************************************/
3522
3523
CPLErr GDALGeoPackageDataset::IBuildOverviews(
3524
    const char *pszResampling, int nOverviews, const int *panOverviewList,
3525
    int nBandsIn, const int * /*panBandList*/, GDALProgressFunc pfnProgress,
3526
    void *pProgressData, CSLConstList papszOptions)
3527
0
{
3528
0
    if (GetAccess() != GA_Update)
3529
0
    {
3530
0
        CPLError(CE_Failure, CPLE_NotSupported,
3531
0
                 "Overview building not supported on a database opened in "
3532
0
                 "read-only mode");
3533
0
        return CE_Failure;
3534
0
    }
3535
0
    if (m_poParentDS != nullptr)
3536
0
    {
3537
0
        CPLError(CE_Failure, CPLE_NotSupported,
3538
0
                 "Overview building not supported on overview dataset");
3539
0
        return CE_Failure;
3540
0
    }
3541
3542
0
    if (nOverviews == 0)
3543
0
    {
3544
0
        for (auto &poOvrDS : m_apoOverviewDS)
3545
0
            poOvrDS->FlushCache(false);
3546
3547
0
        SoftStartTransaction();
3548
3549
0
        if (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT)
3550
0
        {
3551
0
            char *pszSQL = sqlite3_mprintf(
3552
0
                "DELETE FROM gpkg_2d_gridded_tile_ancillary WHERE id IN "
3553
0
                "(SELECT y.id FROM \"%w\" x "
3554
0
                "JOIN gpkg_2d_gridded_tile_ancillary y "
3555
0
                "ON x.id = y.tpudt_id AND y.tpudt_name = '%q' AND "
3556
0
                "x.zoom_level < %d)",
3557
0
                m_osRasterTable.c_str(), m_osRasterTable.c_str(), m_nZoomLevel);
3558
0
            OGRErr eErr = SQLCommand(hDB, pszSQL);
3559
0
            sqlite3_free(pszSQL);
3560
0
            if (eErr != OGRERR_NONE)
3561
0
            {
3562
0
                SoftRollbackTransaction();
3563
0
                return CE_Failure;
3564
0
            }
3565
0
        }
3566
3567
0
        char *pszSQL =
3568
0
            sqlite3_mprintf("DELETE FROM \"%w\" WHERE zoom_level < %d",
3569
0
                            m_osRasterTable.c_str(), m_nZoomLevel);
3570
0
        OGRErr eErr = SQLCommand(hDB, pszSQL);
3571
0
        sqlite3_free(pszSQL);
3572
0
        if (eErr != OGRERR_NONE)
3573
0
        {
3574
0
            SoftRollbackTransaction();
3575
0
            return CE_Failure;
3576
0
        }
3577
3578
0
        SoftCommitTransaction();
3579
3580
0
        return CE_None;
3581
0
    }
3582
3583
0
    if (nBandsIn != nBands)
3584
0
    {
3585
0
        CPLError(CE_Failure, CPLE_NotSupported,
3586
0
                 "Generation of overviews in GPKG only"
3587
0
                 "supported when operating on all bands.");
3588
0
        return CE_Failure;
3589
0
    }
3590
3591
0
    if (m_apoOverviewDS.empty())
3592
0
    {
3593
0
        CPLError(CE_Failure, CPLE_AppDefined,
3594
0
                 "Image too small to support overviews");
3595
0
        return CE_Failure;
3596
0
    }
3597
3598
0
    FlushCache(false);
3599
0
    for (int i = 0; i < nOverviews; i++)
3600
0
    {
3601
0
        if (panOverviewList[i] < 2)
3602
0
        {
3603
0
            CPLError(CE_Failure, CPLE_IllegalArg,
3604
0
                     "Overview factor must be >= 2");
3605
0
            return CE_Failure;
3606
0
        }
3607
3608
0
        bool bFound = false;
3609
0
        int jCandidate = -1;
3610
0
        int nMaxOvFactor = 0;
3611
0
        for (int j = 0; j < static_cast<int>(m_apoOverviewDS.size()); j++)
3612
0
        {
3613
0
            const auto poODS = m_apoOverviewDS[j].get();
3614
0
            const int nOvFactor =
3615
0
                static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3616
3617
0
            nMaxOvFactor = nOvFactor;
3618
3619
0
            if (nOvFactor == panOverviewList[i])
3620
0
            {
3621
0
                bFound = true;
3622
0
                break;
3623
0
            }
3624
3625
0
            if (jCandidate < 0 && nOvFactor > panOverviewList[i])
3626
0
                jCandidate = j;
3627
0
        }
3628
3629
0
        if (!bFound)
3630
0
        {
3631
            /* Mostly for debug */
3632
0
            if (!CPLTestBool(CPLGetConfigOption(
3633
0
                    "ALLOW_GPKG_ZOOM_OTHER_EXTENSION", "YES")))
3634
0
            {
3635
0
                CPLString osOvrList;
3636
0
                for (const auto &poODS : m_apoOverviewDS)
3637
0
                {
3638
0
                    const int nOvFactor =
3639
0
                        static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3640
3641
0
                    if (!osOvrList.empty())
3642
0
                        osOvrList += ' ';
3643
0
                    osOvrList += CPLSPrintf("%d", nOvFactor);
3644
0
                }
3645
0
                CPLError(CE_Failure, CPLE_NotSupported,
3646
0
                         "Only overviews %s can be computed",
3647
0
                         osOvrList.c_str());
3648
0
                return CE_Failure;
3649
0
            }
3650
0
            else
3651
0
            {
3652
0
                int nOvFactor = panOverviewList[i];
3653
0
                if (jCandidate < 0)
3654
0
                    jCandidate = static_cast<int>(m_apoOverviewDS.size());
3655
3656
0
                int nOvXSize = std::max(1, GetRasterXSize() / nOvFactor);
3657
0
                int nOvYSize = std::max(1, GetRasterYSize() / nOvFactor);
3658
0
                if (!(jCandidate == static_cast<int>(m_apoOverviewDS.size()) &&
3659
0
                      nOvFactor == 2 * nMaxOvFactor) &&
3660
0
                    !m_bZoomOther)
3661
0
                {
3662
0
                    CPLError(CE_Warning, CPLE_AppDefined,
3663
0
                             "Use of overview factor %d causes gpkg_zoom_other "
3664
0
                             "extension to be needed",
3665
0
                             nOvFactor);
3666
0
                    RegisterZoomOtherExtension();
3667
0
                    m_bZoomOther = true;
3668
0
                }
3669
3670
0
                SoftStartTransaction();
3671
3672
0
                CPLAssert(jCandidate > 0);
3673
0
                const int nNewZoomLevel =
3674
0
                    m_apoOverviewDS[jCandidate - 1]->m_nZoomLevel;
3675
3676
0
                char *pszSQL;
3677
0
                OGRErr eErr;
3678
0
                for (int k = 0; k <= jCandidate; k++)
3679
0
                {
3680
0
                    pszSQL = sqlite3_mprintf(
3681
0
                        "UPDATE gpkg_tile_matrix SET zoom_level = %d "
3682
0
                        "WHERE lower(table_name) = lower('%q') AND zoom_level "
3683
0
                        "= %d",
3684
0
                        m_nZoomLevel - k + 1, m_osRasterTable.c_str(),
3685
0
                        m_nZoomLevel - k);
3686
0
                    eErr = SQLCommand(hDB, pszSQL);
3687
0
                    sqlite3_free(pszSQL);
3688
0
                    if (eErr != OGRERR_NONE)
3689
0
                    {
3690
0
                        SoftRollbackTransaction();
3691
0
                        return CE_Failure;
3692
0
                    }
3693
3694
0
                    pszSQL =
3695
0
                        sqlite3_mprintf("UPDATE \"%w\" SET zoom_level = %d "
3696
0
                                        "WHERE zoom_level = %d",
3697
0
                                        m_osRasterTable.c_str(),
3698
0
                                        m_nZoomLevel - k + 1, m_nZoomLevel - k);
3699
0
                    eErr = SQLCommand(hDB, pszSQL);
3700
0
                    sqlite3_free(pszSQL);
3701
0
                    if (eErr != OGRERR_NONE)
3702
0
                    {
3703
0
                        SoftRollbackTransaction();
3704
0
                        return CE_Failure;
3705
0
                    }
3706
0
                }
3707
3708
0
                double dfGDALMinX = m_gt[0];
3709
0
                double dfGDALMinY = m_gt[3] + nRasterYSize * m_gt[5];
3710
0
                double dfGDALMaxX = m_gt[0] + nRasterXSize * m_gt[1];
3711
0
                double dfGDALMaxY = m_gt[3];
3712
0
                double dfPixelXSizeZoomLevel = m_gt[1] * nOvFactor;
3713
0
                double dfPixelYSizeZoomLevel = fabs(m_gt[5]) * nOvFactor;
3714
0
                int nTileWidth, nTileHeight;
3715
0
                GetRasterBand(1)->GetBlockSize(&nTileWidth, &nTileHeight);
3716
0
                int nTileMatrixWidth = DIV_ROUND_UP(nOvXSize, nTileWidth);
3717
0
                int nTileMatrixHeight = DIV_ROUND_UP(nOvYSize, nTileHeight);
3718
0
                pszSQL = sqlite3_mprintf(
3719
0
                    "INSERT INTO gpkg_tile_matrix "
3720
0
                    "(table_name,zoom_level,matrix_width,matrix_height,tile_"
3721
0
                    "width,tile_height,pixel_x_size,pixel_y_size) VALUES "
3722
0
                    "('%q',%d,%d,%d,%d,%d,%.17g,%.17g)",
3723
0
                    m_osRasterTable.c_str(), nNewZoomLevel, nTileMatrixWidth,
3724
0
                    nTileMatrixHeight, nTileWidth, nTileHeight,
3725
0
                    dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel);
3726
0
                eErr = SQLCommand(hDB, pszSQL);
3727
0
                sqlite3_free(pszSQL);
3728
0
                if (eErr != OGRERR_NONE)
3729
0
                {
3730
0
                    SoftRollbackTransaction();
3731
0
                    return CE_Failure;
3732
0
                }
3733
3734
0
                SoftCommitTransaction();
3735
3736
0
                m_nZoomLevel++; /* this change our zoom level as well as
3737
                                   previous overviews */
3738
0
                for (int k = 0; k < jCandidate; k++)
3739
0
                    m_apoOverviewDS[k]->m_nZoomLevel++;
3740
3741
0
                auto poOvrDS = std::make_unique<GDALGeoPackageDataset>();
3742
0
                poOvrDS->ShareLockWithParentDataset(this);
3743
0
                poOvrDS->InitRaster(
3744
0
                    this, m_osRasterTable, nNewZoomLevel, nBands, m_dfTMSMinX,
3745
0
                    m_dfTMSMaxY, dfPixelXSizeZoomLevel, dfPixelYSizeZoomLevel,
3746
0
                    nTileWidth, nTileHeight, nTileMatrixWidth,
3747
0
                    nTileMatrixHeight, dfGDALMinX, dfGDALMinY, dfGDALMaxX,
3748
0
                    dfGDALMaxY);
3749
0
                m_apoOverviewDS.insert(m_apoOverviewDS.begin() + jCandidate,
3750
0
                                       std::move(poOvrDS));
3751
0
            }
3752
0
        }
3753
0
    }
3754
3755
0
    GDALRasterBand ***papapoOverviewBands = static_cast<GDALRasterBand ***>(
3756
0
        CPLCalloc(sizeof(GDALRasterBand **), nBands));
3757
0
    CPLErr eErr = CE_None;
3758
0
    for (int iBand = 0; eErr == CE_None && iBand < nBands; iBand++)
3759
0
    {
3760
0
        papapoOverviewBands[iBand] = static_cast<GDALRasterBand **>(
3761
0
            CPLCalloc(sizeof(GDALRasterBand *), nOverviews));
3762
0
        int iCurOverview = 0;
3763
0
        for (int i = 0; i < nOverviews; i++)
3764
0
        {
3765
0
            bool bFound = false;
3766
0
            for (const auto &poODS : m_apoOverviewDS)
3767
0
            {
3768
0
                const int nOvFactor =
3769
0
                    static_cast<int>(0.5 + poODS->m_gt[1] / m_gt[1]);
3770
3771
0
                if (nOvFactor == panOverviewList[i])
3772
0
                {
3773
0
                    papapoOverviewBands[iBand][iCurOverview] =
3774
0
                        poODS->GetRasterBand(iBand + 1);
3775
0
                    iCurOverview++;
3776
0
                    bFound = true;
3777
0
                    break;
3778
0
                }
3779
0
            }
3780
0
            if (!bFound)
3781
0
            {
3782
0
                CPLError(CE_Failure, CPLE_AppDefined,
3783
0
                         "Could not find dataset corresponding to ov factor %d",
3784
0
                         panOverviewList[i]);
3785
0
                eErr = CE_Failure;
3786
0
            }
3787
0
        }
3788
0
        if (eErr == CE_None)
3789
0
        {
3790
0
            CPLAssert(iCurOverview == nOverviews);
3791
0
        }
3792
0
    }
3793
3794
0
    if (eErr == CE_None)
3795
0
        eErr = GDALRegenerateOverviewsMultiBand(
3796
0
            nBands, papoBands, nOverviews, papapoOverviewBands, pszResampling,
3797
0
            pfnProgress, pProgressData, papszOptions);
3798
3799
0
    for (int iBand = 0; iBand < nBands; iBand++)
3800
0
    {
3801
0
        CPLFree(papapoOverviewBands[iBand]);
3802
0
    }
3803
0
    CPLFree(papapoOverviewBands);
3804
3805
0
    return eErr;
3806
0
}
3807
3808
/************************************************************************/
3809
/*                            GetFileList()                             */
3810
/************************************************************************/
3811
3812
char **GDALGeoPackageDataset::GetFileList()
3813
48
{
3814
48
    TryLoadXML();
3815
48
    return GDALPamDataset::GetFileList();
3816
48
}
3817
3818
/************************************************************************/
3819
/*                      GetMetadataDomainList()                         */
3820
/************************************************************************/
3821
3822
char **GDALGeoPackageDataset::GetMetadataDomainList()
3823
0
{
3824
0
    GetMetadata();
3825
0
    if (!m_osRasterTable.empty())
3826
0
        GetMetadata("GEOPACKAGE");
3827
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
3828
0
                                   TRUE, "SUBDATASETS", nullptr);
3829
0
}
3830
3831
/************************************************************************/
3832
/*                        CheckMetadataDomain()                         */
3833
/************************************************************************/
3834
3835
const char *GDALGeoPackageDataset::CheckMetadataDomain(const char *pszDomain)
3836
9.72k
{
3837
9.72k
    if (pszDomain != nullptr && EQUAL(pszDomain, "GEOPACKAGE") &&
3838
0
        m_osRasterTable.empty())
3839
0
    {
3840
0
        CPLError(
3841
0
            CE_Warning, CPLE_IllegalArg,
3842
0
            "Using GEOPACKAGE for a non-raster geopackage is not supported. "
3843
0
            "Using default domain instead");
3844
0
        return nullptr;
3845
0
    }
3846
9.72k
    return pszDomain;
3847
9.72k
}
3848
3849
/************************************************************************/
3850
/*                           HasMetadataTables()                        */
3851
/************************************************************************/
3852
3853
bool GDALGeoPackageDataset::HasMetadataTables() const
3854
7.18k
{
3855
7.18k
    if (m_nHasMetadataTables < 0)
3856
2.38k
    {
3857
2.38k
        const int nCount =
3858
2.38k
            SQLGetInteger(hDB,
3859
2.38k
                          "SELECT COUNT(*) FROM sqlite_master WHERE name IN "
3860
2.38k
                          "('gpkg_metadata', 'gpkg_metadata_reference') "
3861
2.38k
                          "AND type IN ('table', 'view')",
3862
2.38k
                          nullptr);
3863
2.38k
        m_nHasMetadataTables = nCount == 2;
3864
2.38k
    }
3865
7.18k
    return CPL_TO_BOOL(m_nHasMetadataTables);
3866
7.18k
}
3867
3868
/************************************************************************/
3869
/*                         HasDataColumnsTable()                        */
3870
/************************************************************************/
3871
3872
bool GDALGeoPackageDataset::HasDataColumnsTable() const
3873
1.12k
{
3874
1.12k
    const int nCount = SQLGetInteger(
3875
1.12k
        hDB,
3876
1.12k
        "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_data_columns'"
3877
1.12k
        "AND type IN ('table', 'view')",
3878
1.12k
        nullptr);
3879
1.12k
    return nCount == 1;
3880
1.12k
}
3881
3882
/************************************************************************/
3883
/*                    HasDataColumnConstraintsTable()                   */
3884
/************************************************************************/
3885
3886
bool GDALGeoPackageDataset::HasDataColumnConstraintsTable() const
3887
0
{
3888
0
    const int nCount = SQLGetInteger(hDB,
3889
0
                                     "SELECT 1 FROM sqlite_master WHERE name = "
3890
0
                                     "'gpkg_data_column_constraints'"
3891
0
                                     "AND type IN ('table', 'view')",
3892
0
                                     nullptr);
3893
0
    return nCount == 1;
3894
0
}
3895
3896
/************************************************************************/
3897
/*                  HasDataColumnConstraintsTableGPKG_1_0()             */
3898
/************************************************************************/
3899
3900
bool GDALGeoPackageDataset::HasDataColumnConstraintsTableGPKG_1_0() const
3901
0
{
3902
0
    if (m_nApplicationId != GP10_APPLICATION_ID)
3903
0
        return false;
3904
    // In GPKG 1.0, the columns were named minIsInclusive, maxIsInclusive
3905
    // They were changed in 1.1 to min_is_inclusive, max_is_inclusive
3906
0
    bool bRet = false;
3907
0
    sqlite3_stmt *hSQLStmt = nullptr;
3908
0
    int rc = sqlite3_prepare_v2(hDB,
3909
0
                                "SELECT minIsInclusive, maxIsInclusive FROM "
3910
0
                                "gpkg_data_column_constraints",
3911
0
                                -1, &hSQLStmt, nullptr);
3912
0
    if (rc == SQLITE_OK)
3913
0
    {
3914
0
        bRet = true;
3915
0
        sqlite3_finalize(hSQLStmt);
3916
0
    }
3917
0
    return bRet;
3918
0
}
3919
3920
/************************************************************************/
3921
/*      CreateColumnsTableAndColumnConstraintsTablesIfNecessary()       */
3922
/************************************************************************/
3923
3924
bool GDALGeoPackageDataset::
3925
    CreateColumnsTableAndColumnConstraintsTablesIfNecessary()
3926
0
{
3927
0
    if (!HasDataColumnsTable())
3928
0
    {
3929
        // Geopackage < 1.3 had
3930
        // CONSTRAINT fk_gdc_tn FOREIGN KEY (table_name) REFERENCES
3931
        // gpkg_contents(table_name) instead of the unique constraint.
3932
0
        if (OGRERR_NONE !=
3933
0
            SQLCommand(
3934
0
                GetDB(),
3935
0
                "CREATE TABLE gpkg_data_columns ("
3936
0
                "table_name TEXT NOT NULL,"
3937
0
                "column_name TEXT NOT NULL,"
3938
0
                "name TEXT,"
3939
0
                "title TEXT,"
3940
0
                "description TEXT,"
3941
0
                "mime_type TEXT,"
3942
0
                "constraint_name TEXT,"
3943
0
                "CONSTRAINT pk_gdc PRIMARY KEY (table_name, column_name),"
3944
0
                "CONSTRAINT gdc_tn UNIQUE (table_name, name));"))
3945
0
        {
3946
0
            return false;
3947
0
        }
3948
0
    }
3949
0
    if (!HasDataColumnConstraintsTable())
3950
0
    {
3951
0
        const char *min_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3952
0
                                           ? "min_is_inclusive"
3953
0
                                           : "minIsInclusive";
3954
0
        const char *max_is_inclusive = m_nApplicationId != GP10_APPLICATION_ID
3955
0
                                           ? "max_is_inclusive"
3956
0
                                           : "maxIsInclusive";
3957
3958
0
        const std::string osSQL(
3959
0
            CPLSPrintf("CREATE TABLE gpkg_data_column_constraints ("
3960
0
                       "constraint_name TEXT NOT NULL,"
3961
0
                       "constraint_type TEXT NOT NULL,"
3962
0
                       "value TEXT,"
3963
0
                       "min NUMERIC,"
3964
0
                       "%s BOOLEAN,"
3965
0
                       "max NUMERIC,"
3966
0
                       "%s BOOLEAN,"
3967
0
                       "description TEXT,"
3968
0
                       "CONSTRAINT gdcc_ntv UNIQUE (constraint_name, "
3969
0
                       "constraint_type, value));",
3970
0
                       min_is_inclusive, max_is_inclusive));
3971
0
        if (OGRERR_NONE != SQLCommand(GetDB(), osSQL.c_str()))
3972
0
        {
3973
0
            return false;
3974
0
        }
3975
0
    }
3976
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
3977
0
    {
3978
0
        return false;
3979
0
    }
3980
0
    if (SQLGetInteger(GetDB(),
3981
0
                      "SELECT 1 FROM gpkg_extensions WHERE "
3982
0
                      "table_name = 'gpkg_data_columns'",
3983
0
                      nullptr) != 1)
3984
0
    {
3985
0
        if (OGRERR_NONE !=
3986
0
            SQLCommand(
3987
0
                GetDB(),
3988
0
                "INSERT INTO gpkg_extensions "
3989
0
                "(table_name,column_name,extension_name,definition,scope) "
3990
0
                "VALUES ('gpkg_data_columns', NULL, 'gpkg_schema', "
3991
0
                "'http://www.geopackage.org/spec121/#extension_schema', "
3992
0
                "'read-write')"))
3993
0
        {
3994
0
            return false;
3995
0
        }
3996
0
    }
3997
0
    if (SQLGetInteger(GetDB(),
3998
0
                      "SELECT 1 FROM gpkg_extensions WHERE "
3999
0
                      "table_name = 'gpkg_data_column_constraints'",
4000
0
                      nullptr) != 1)
4001
0
    {
4002
0
        if (OGRERR_NONE !=
4003
0
            SQLCommand(
4004
0
                GetDB(),
4005
0
                "INSERT INTO gpkg_extensions "
4006
0
                "(table_name,column_name,extension_name,definition,scope) "
4007
0
                "VALUES ('gpkg_data_column_constraints', NULL, 'gpkg_schema', "
4008
0
                "'http://www.geopackage.org/spec121/#extension_schema', "
4009
0
                "'read-write')"))
4010
0
        {
4011
0
            return false;
4012
0
        }
4013
0
    }
4014
4015
0
    return true;
4016
0
}
4017
4018
/************************************************************************/
4019
/*                        HasGpkgextRelationsTable()                    */
4020
/************************************************************************/
4021
4022
bool GDALGeoPackageDataset::HasGpkgextRelationsTable() const
4023
590
{
4024
590
    const int nCount = SQLGetInteger(
4025
590
        hDB,
4026
590
        "SELECT 1 FROM sqlite_master WHERE name = 'gpkgext_relations'"
4027
590
        "AND type IN ('table', 'view')",
4028
590
        nullptr);
4029
590
    return nCount == 1;
4030
590
}
4031
4032
/************************************************************************/
4033
/*                    CreateRelationsTableIfNecessary()                 */
4034
/************************************************************************/
4035
4036
bool GDALGeoPackageDataset::CreateRelationsTableIfNecessary()
4037
0
{
4038
0
    if (HasGpkgextRelationsTable())
4039
0
    {
4040
0
        return true;
4041
0
    }
4042
4043
0
    if (OGRERR_NONE !=
4044
0
        SQLCommand(GetDB(), "CREATE TABLE gpkgext_relations ("
4045
0
                            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
4046
0
                            "base_table_name TEXT NOT NULL,"
4047
0
                            "base_primary_column TEXT NOT NULL DEFAULT 'id',"
4048
0
                            "related_table_name TEXT NOT NULL,"
4049
0
                            "related_primary_column TEXT NOT NULL DEFAULT 'id',"
4050
0
                            "relation_name TEXT NOT NULL,"
4051
0
                            "mapping_table_name TEXT NOT NULL UNIQUE);"))
4052
0
    {
4053
0
        return false;
4054
0
    }
4055
4056
0
    return true;
4057
0
}
4058
4059
/************************************************************************/
4060
/*                        HasQGISLayerStyles()                          */
4061
/************************************************************************/
4062
4063
bool GDALGeoPackageDataset::HasQGISLayerStyles() const
4064
0
{
4065
    // QGIS layer_styles extension:
4066
    // https://github.com/pka/qgpkg/blob/master/qgis_geopackage_extension.md
4067
0
    bool bRet = false;
4068
0
    const int nCount =
4069
0
        SQLGetInteger(hDB,
4070
0
                      "SELECT 1 FROM sqlite_master WHERE name = 'layer_styles'"
4071
0
                      "AND type = 'table'",
4072
0
                      nullptr);
4073
0
    if (nCount == 1)
4074
0
    {
4075
0
        sqlite3_stmt *hSQLStmt = nullptr;
4076
0
        int rc = sqlite3_prepare_v2(
4077
0
            hDB, "SELECT f_table_name, f_geometry_column FROM layer_styles", -1,
4078
0
            &hSQLStmt, nullptr);
4079
0
        if (rc == SQLITE_OK)
4080
0
        {
4081
0
            bRet = true;
4082
0
            sqlite3_finalize(hSQLStmt);
4083
0
        }
4084
0
    }
4085
0
    return bRet;
4086
0
}
4087
4088
/************************************************************************/
4089
/*                            GetMetadata()                             */
4090
/************************************************************************/
4091
4092
char **GDALGeoPackageDataset::GetMetadata(const char *pszDomain)
4093
4094
5.83k
{
4095
5.83k
    pszDomain = CheckMetadataDomain(pszDomain);
4096
5.83k
    if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
4097
0
        return m_aosSubDatasets.List();
4098
4099
5.83k
    if (m_bHasReadMetadataFromStorage)
4100
3.44k
        return GDALPamDataset::GetMetadata(pszDomain);
4101
4102
2.38k
    m_bHasReadMetadataFromStorage = true;
4103
4104
2.38k
    TryLoadXML();
4105
4106
2.38k
    if (!HasMetadataTables())
4107
2.38k
        return GDALPamDataset::GetMetadata(pszDomain);
4108
4109
2
    char *pszSQL = nullptr;
4110
2
    if (!m_osRasterTable.empty())
4111
0
    {
4112
0
        pszSQL = sqlite3_mprintf(
4113
0
            "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4114
0
            "mdr.reference_scope FROM gpkg_metadata md "
4115
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4116
0
            "WHERE "
4117
0
            "(mdr.reference_scope = 'geopackage' OR "
4118
0
            "(mdr.reference_scope = 'table' AND lower(mdr.table_name) = "
4119
0
            "lower('%q'))) ORDER BY md.id "
4120
0
            "LIMIT 1000",  // to avoid denial of service
4121
0
            m_osRasterTable.c_str());
4122
0
    }
4123
2
    else
4124
2
    {
4125
2
        pszSQL = sqlite3_mprintf(
4126
2
            "SELECT md.metadata, md.md_standard_uri, md.mime_type, "
4127
2
            "mdr.reference_scope FROM gpkg_metadata md "
4128
2
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4129
2
            "WHERE "
4130
2
            "mdr.reference_scope = 'geopackage' ORDER BY md.id "
4131
2
            "LIMIT 1000"  // to avoid denial of service
4132
2
        );
4133
2
    }
4134
4135
2
    auto oResult = SQLQuery(hDB, pszSQL);
4136
2
    sqlite3_free(pszSQL);
4137
2
    if (!oResult)
4138
0
    {
4139
0
        return GDALPamDataset::GetMetadata(pszDomain);
4140
0
    }
4141
4142
2
    char **papszMetadata = CSLDuplicate(GDALPamDataset::GetMetadata());
4143
4144
    /* GDAL metadata */
4145
2
    for (int i = 0; i < oResult->RowCount(); i++)
4146
0
    {
4147
0
        const char *pszMetadata = oResult->GetValue(0, i);
4148
0
        const char *pszMDStandardURI = oResult->GetValue(1, i);
4149
0
        const char *pszMimeType = oResult->GetValue(2, i);
4150
0
        const char *pszReferenceScope = oResult->GetValue(3, i);
4151
0
        if (pszMetadata && pszMDStandardURI && pszMimeType &&
4152
0
            pszReferenceScope && EQUAL(pszMDStandardURI, "http://gdal.org") &&
4153
0
            EQUAL(pszMimeType, "text/xml"))
4154
0
        {
4155
0
            CPLXMLNode *psXMLNode = CPLParseXMLString(pszMetadata);
4156
0
            if (psXMLNode)
4157
0
            {
4158
0
                GDALMultiDomainMetadata oLocalMDMD;
4159
0
                oLocalMDMD.XMLInit(psXMLNode, FALSE);
4160
0
                if (!m_osRasterTable.empty() &&
4161
0
                    EQUAL(pszReferenceScope, "geopackage"))
4162
0
                {
4163
0
                    oMDMD.SetMetadata(oLocalMDMD.GetMetadata(), "GEOPACKAGE");
4164
0
                }
4165
0
                else
4166
0
                {
4167
0
                    papszMetadata =
4168
0
                        CSLMerge(papszMetadata, oLocalMDMD.GetMetadata());
4169
0
                    CSLConstList papszDomainList = oLocalMDMD.GetDomainList();
4170
0
                    CSLConstList papszIter = papszDomainList;
4171
0
                    while (papszIter && *papszIter)
4172
0
                    {
4173
0
                        if (EQUAL(*papszIter, "IMAGE_STRUCTURE"))
4174
0
                        {
4175
0
                            CSLConstList papszMD =
4176
0
                                oLocalMDMD.GetMetadata(*papszIter);
4177
0
                            const char *pszBAND_COUNT =
4178
0
                                CSLFetchNameValue(papszMD, "BAND_COUNT");
4179
0
                            if (pszBAND_COUNT)
4180
0
                                m_nBandCountFromMetadata = atoi(pszBAND_COUNT);
4181
4182
0
                            const char *pszCOLOR_TABLE =
4183
0
                                CSLFetchNameValue(papszMD, "COLOR_TABLE");
4184
0
                            if (pszCOLOR_TABLE)
4185
0
                            {
4186
0
                                const CPLStringList aosTokens(
4187
0
                                    CSLTokenizeString2(pszCOLOR_TABLE, "{,",
4188
0
                                                       0));
4189
0
                                if ((aosTokens.size() % 4) == 0)
4190
0
                                {
4191
0
                                    const int nColors = aosTokens.size() / 4;
4192
0
                                    m_poCTFromMetadata =
4193
0
                                        std::make_unique<GDALColorTable>();
4194
0
                                    for (int iColor = 0; iColor < nColors;
4195
0
                                         ++iColor)
4196
0
                                    {
4197
0
                                        GDALColorEntry sEntry;
4198
0
                                        sEntry.c1 = static_cast<short>(
4199
0
                                            atoi(aosTokens[4 * iColor + 0]));
4200
0
                                        sEntry.c2 = static_cast<short>(
4201
0
                                            atoi(aosTokens[4 * iColor + 1]));
4202
0
                                        sEntry.c3 = static_cast<short>(
4203
0
                                            atoi(aosTokens[4 * iColor + 2]));
4204
0
                                        sEntry.c4 = static_cast<short>(
4205
0
                                            atoi(aosTokens[4 * iColor + 3]));
4206
0
                                        m_poCTFromMetadata->SetColorEntry(
4207
0
                                            iColor, &sEntry);
4208
0
                                    }
4209
0
                                }
4210
0
                            }
4211
4212
0
                            const char *pszTILE_FORMAT =
4213
0
                                CSLFetchNameValue(papszMD, "TILE_FORMAT");
4214
0
                            if (pszTILE_FORMAT)
4215
0
                            {
4216
0
                                m_osTFFromMetadata = pszTILE_FORMAT;
4217
0
                                oMDMD.SetMetadataItem("TILE_FORMAT",
4218
0
                                                      pszTILE_FORMAT,
4219
0
                                                      "IMAGE_STRUCTURE");
4220
0
                            }
4221
4222
0
                            const char *pszNodataValue =
4223
0
                                CSLFetchNameValue(papszMD, "NODATA_VALUE");
4224
0
                            if (pszNodataValue)
4225
0
                            {
4226
0
                                m_osNodataValueFromMetadata = pszNodataValue;
4227
0
                            }
4228
0
                        }
4229
4230
0
                        else if (!EQUAL(*papszIter, "") &&
4231
0
                                 !STARTS_WITH(*papszIter, "BAND_"))
4232
0
                        {
4233
0
                            oMDMD.SetMetadata(
4234
0
                                oLocalMDMD.GetMetadata(*papszIter), *papszIter);
4235
0
                        }
4236
0
                        papszIter++;
4237
0
                    }
4238
0
                }
4239
0
                CPLDestroyXMLNode(psXMLNode);
4240
0
            }
4241
0
        }
4242
0
    }
4243
4244
2
    GDALPamDataset::SetMetadata(papszMetadata);
4245
2
    CSLDestroy(papszMetadata);
4246
2
    papszMetadata = nullptr;
4247
4248
    /* Add non-GDAL metadata now */
4249
2
    int nNonGDALMDILocal = 1;
4250
2
    int nNonGDALMDIGeopackage = 1;
4251
2
    for (int i = 0; i < oResult->RowCount(); i++)
4252
0
    {
4253
0
        const char *pszMetadata = oResult->GetValue(0, i);
4254
0
        const char *pszMDStandardURI = oResult->GetValue(1, i);
4255
0
        const char *pszMimeType = oResult->GetValue(2, i);
4256
0
        const char *pszReferenceScope = oResult->GetValue(3, i);
4257
0
        if (pszMetadata == nullptr || pszMDStandardURI == nullptr ||
4258
0
            pszMimeType == nullptr || pszReferenceScope == nullptr)
4259
0
        {
4260
            // should not happen as there are NOT NULL constraints
4261
            // But a database could lack such NOT NULL constraints or have
4262
            // large values that would cause a memory allocation failure.
4263
0
            continue;
4264
0
        }
4265
0
        int bIsGPKGScope = EQUAL(pszReferenceScope, "geopackage");
4266
0
        if (EQUAL(pszMDStandardURI, "http://gdal.org") &&
4267
0
            EQUAL(pszMimeType, "text/xml"))
4268
0
            continue;
4269
4270
0
        if (!m_osRasterTable.empty() && bIsGPKGScope)
4271
0
        {
4272
0
            oMDMD.SetMetadataItem(
4273
0
                CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDIGeopackage),
4274
0
                pszMetadata, "GEOPACKAGE");
4275
0
            nNonGDALMDIGeopackage++;
4276
0
        }
4277
        /*else if( strcmp( pszMDStandardURI, "http://www.isotc211.org/2005/gmd"
4278
        ) == 0 && strcmp( pszMimeType, "text/xml" ) == 0 )
4279
        {
4280
            char* apszMD[2];
4281
            apszMD[0] = (char*)pszMetadata;
4282
            apszMD[1] = NULL;
4283
            oMDMD.SetMetadata(apszMD, "xml:MD_Metadata");
4284
        }*/
4285
0
        else
4286
0
        {
4287
0
            oMDMD.SetMetadataItem(
4288
0
                CPLSPrintf("GPKG_METADATA_ITEM_%d", nNonGDALMDILocal),
4289
0
                pszMetadata);
4290
0
            nNonGDALMDILocal++;
4291
0
        }
4292
0
    }
4293
4294
2
    return GDALPamDataset::GetMetadata(pszDomain);
4295
2
}
4296
4297
/************************************************************************/
4298
/*                            WriteMetadata()                           */
4299
/************************************************************************/
4300
4301
void GDALGeoPackageDataset::WriteMetadata(
4302
    CPLXMLNode *psXMLNode, /* will be destroyed by the method */
4303
    const char *pszTableName)
4304
0
{
4305
0
    const bool bIsEmpty = (psXMLNode == nullptr);
4306
0
    if (!HasMetadataTables())
4307
0
    {
4308
0
        if (bIsEmpty || !CreateMetadataTables())
4309
0
        {
4310
0
            CPLDestroyXMLNode(psXMLNode);
4311
0
            return;
4312
0
        }
4313
0
    }
4314
4315
0
    char *pszXML = nullptr;
4316
0
    if (!bIsEmpty)
4317
0
    {
4318
0
        CPLXMLNode *psMasterXMLNode =
4319
0
            CPLCreateXMLNode(nullptr, CXT_Element, "GDALMultiDomainMetadata");
4320
0
        psMasterXMLNode->psChild = psXMLNode;
4321
0
        pszXML = CPLSerializeXMLTree(psMasterXMLNode);
4322
0
        CPLDestroyXMLNode(psMasterXMLNode);
4323
0
    }
4324
    // cppcheck-suppress uselessAssignmentPtrArg
4325
0
    psXMLNode = nullptr;
4326
4327
0
    char *pszSQL = nullptr;
4328
0
    if (pszTableName && pszTableName[0] != '\0')
4329
0
    {
4330
0
        pszSQL = sqlite3_mprintf(
4331
0
            "SELECT md.id FROM gpkg_metadata md "
4332
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4333
0
            "WHERE md.md_scope = 'dataset' AND "
4334
0
            "md.md_standard_uri='http://gdal.org' "
4335
0
            "AND md.mime_type='text/xml' AND mdr.reference_scope = 'table' AND "
4336
0
            "lower(mdr.table_name) = lower('%q')",
4337
0
            pszTableName);
4338
0
    }
4339
0
    else
4340
0
    {
4341
0
        pszSQL = sqlite3_mprintf(
4342
0
            "SELECT md.id FROM gpkg_metadata md "
4343
0
            "JOIN gpkg_metadata_reference mdr ON (md.id = mdr.md_file_id ) "
4344
0
            "WHERE md.md_scope = 'dataset' AND "
4345
0
            "md.md_standard_uri='http://gdal.org' "
4346
0
            "AND md.mime_type='text/xml' AND mdr.reference_scope = "
4347
0
            "'geopackage'");
4348
0
    }
4349
0
    OGRErr err;
4350
0
    int mdId = SQLGetInteger(hDB, pszSQL, &err);
4351
0
    if (err != OGRERR_NONE)
4352
0
        mdId = -1;
4353
0
    sqlite3_free(pszSQL);
4354
4355
0
    if (bIsEmpty)
4356
0
    {
4357
0
        if (mdId >= 0)
4358
0
        {
4359
0
            SQLCommand(
4360
0
                hDB,
4361
0
                CPLSPrintf(
4362
0
                    "DELETE FROM gpkg_metadata_reference WHERE md_file_id = %d",
4363
0
                    mdId));
4364
0
            SQLCommand(
4365
0
                hDB,
4366
0
                CPLSPrintf("DELETE FROM gpkg_metadata WHERE id = %d", mdId));
4367
0
        }
4368
0
    }
4369
0
    else
4370
0
    {
4371
0
        if (mdId >= 0)
4372
0
        {
4373
0
            pszSQL = sqlite3_mprintf(
4374
0
                "UPDATE gpkg_metadata SET metadata = '%q' WHERE id = %d",
4375
0
                pszXML, mdId);
4376
0
        }
4377
0
        else
4378
0
        {
4379
0
            pszSQL =
4380
0
                sqlite3_mprintf("INSERT INTO gpkg_metadata (md_scope, "
4381
0
                                "md_standard_uri, mime_type, metadata) VALUES "
4382
0
                                "('dataset','http://gdal.org','text/xml','%q')",
4383
0
                                pszXML);
4384
0
        }
4385
0
        SQLCommand(hDB, pszSQL);
4386
0
        sqlite3_free(pszSQL);
4387
4388
0
        CPLFree(pszXML);
4389
4390
0
        if (mdId < 0)
4391
0
        {
4392
0
            const sqlite_int64 nFID = sqlite3_last_insert_rowid(hDB);
4393
0
            if (pszTableName != nullptr && pszTableName[0] != '\0')
4394
0
            {
4395
0
                pszSQL = sqlite3_mprintf(
4396
0
                    "INSERT INTO gpkg_metadata_reference (reference_scope, "
4397
0
                    "table_name, timestamp, md_file_id) VALUES "
4398
0
                    "('table', '%q', %s, %d)",
4399
0
                    pszTableName, GetCurrentDateEscapedSQL().c_str(),
4400
0
                    static_cast<int>(nFID));
4401
0
            }
4402
0
            else
4403
0
            {
4404
0
                pszSQL = sqlite3_mprintf(
4405
0
                    "INSERT INTO gpkg_metadata_reference (reference_scope, "
4406
0
                    "timestamp, md_file_id) VALUES "
4407
0
                    "('geopackage', %s, %d)",
4408
0
                    GetCurrentDateEscapedSQL().c_str(), static_cast<int>(nFID));
4409
0
            }
4410
0
        }
4411
0
        else
4412
0
        {
4413
0
            pszSQL = sqlite3_mprintf("UPDATE gpkg_metadata_reference SET "
4414
0
                                     "timestamp = %s WHERE md_file_id = %d",
4415
0
                                     GetCurrentDateEscapedSQL().c_str(), mdId);
4416
0
        }
4417
0
        SQLCommand(hDB, pszSQL);
4418
0
        sqlite3_free(pszSQL);
4419
0
    }
4420
0
}
4421
4422
/************************************************************************/
4423
/*                        CreateMetadataTables()                        */
4424
/************************************************************************/
4425
4426
bool GDALGeoPackageDataset::CreateMetadataTables()
4427
0
{
4428
0
    const bool bCreateTriggers =
4429
0
        CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "NO"));
4430
4431
    /* From C.10. gpkg_metadata Table 35. gpkg_metadata Table Definition SQL  */
4432
0
    CPLString osSQL = "CREATE TABLE gpkg_metadata ("
4433
0
                      "id INTEGER CONSTRAINT m_pk PRIMARY KEY ASC NOT NULL,"
4434
0
                      "md_scope TEXT NOT NULL DEFAULT 'dataset',"
4435
0
                      "md_standard_uri TEXT NOT NULL,"
4436
0
                      "mime_type TEXT NOT NULL DEFAULT 'text/xml',"
4437
0
                      "metadata TEXT NOT NULL DEFAULT ''"
4438
0
                      ")";
4439
4440
    /* From D.2. metadata Table 40. metadata Trigger Definition SQL  */
4441
0
    const char *pszMetadataTriggers =
4442
0
        "CREATE TRIGGER 'gpkg_metadata_md_scope_insert' "
4443
0
        "BEFORE INSERT ON 'gpkg_metadata' "
4444
0
        "FOR EACH ROW BEGIN "
4445
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata violates "
4446
0
        "constraint: md_scope must be one of undefined | fieldSession | "
4447
0
        "collectionSession | series | dataset | featureType | feature | "
4448
0
        "attributeType | attribute | tile | model | catalogue | schema | "
4449
0
        "taxonomy software | service | collectionHardware | "
4450
0
        "nonGeographicDataset | dimensionGroup') "
4451
0
        "WHERE NOT(NEW.md_scope IN "
4452
0
        "('undefined','fieldSession','collectionSession','series','dataset', "
4453
0
        "'featureType','feature','attributeType','attribute','tile','model', "
4454
0
        "'catalogue','schema','taxonomy','software','service', "
4455
0
        "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4456
0
        "END; "
4457
0
        "CREATE TRIGGER 'gpkg_metadata_md_scope_update' "
4458
0
        "BEFORE UPDATE OF 'md_scope' ON 'gpkg_metadata' "
4459
0
        "FOR EACH ROW BEGIN "
4460
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata violates "
4461
0
        "constraint: md_scope must be one of undefined | fieldSession | "
4462
0
        "collectionSession | series | dataset | featureType | feature | "
4463
0
        "attributeType | attribute | tile | model | catalogue | schema | "
4464
0
        "taxonomy software | service | collectionHardware | "
4465
0
        "nonGeographicDataset | dimensionGroup') "
4466
0
        "WHERE NOT(NEW.md_scope IN "
4467
0
        "('undefined','fieldSession','collectionSession','series','dataset', "
4468
0
        "'featureType','feature','attributeType','attribute','tile','model', "
4469
0
        "'catalogue','schema','taxonomy','software','service', "
4470
0
        "'collectionHardware','nonGeographicDataset','dimensionGroup')); "
4471
0
        "END";
4472
0
    if (bCreateTriggers)
4473
0
    {
4474
0
        osSQL += ";";
4475
0
        osSQL += pszMetadataTriggers;
4476
0
    }
4477
4478
    /* From C.11. gpkg_metadata_reference Table 36. gpkg_metadata_reference
4479
     * Table Definition SQL */
4480
0
    osSQL += ";"
4481
0
             "CREATE TABLE gpkg_metadata_reference ("
4482
0
             "reference_scope TEXT NOT NULL,"
4483
0
             "table_name TEXT,"
4484
0
             "column_name TEXT,"
4485
0
             "row_id_value INTEGER,"
4486
0
             "timestamp DATETIME NOT NULL DEFAULT "
4487
0
             "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
4488
0
             "md_file_id INTEGER NOT NULL,"
4489
0
             "md_parent_id INTEGER,"
4490
0
             "CONSTRAINT crmr_mfi_fk FOREIGN KEY (md_file_id) REFERENCES "
4491
0
             "gpkg_metadata(id),"
4492
0
             "CONSTRAINT crmr_mpi_fk FOREIGN KEY (md_parent_id) REFERENCES "
4493
0
             "gpkg_metadata(id)"
4494
0
             ")";
4495
4496
    /* From D.3. metadata_reference Table 41. gpkg_metadata_reference Trigger
4497
     * Definition SQL   */
4498
0
    const char *pszMetadataReferenceTriggers =
4499
0
        "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_insert' "
4500
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4501
0
        "FOR EACH ROW BEGIN "
4502
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4503
0
        "violates constraint: reference_scope must be one of \"geopackage\", "
4504
0
        "table\", \"column\", \"row\", \"row/col\"') "
4505
0
        "WHERE NOT NEW.reference_scope IN "
4506
0
        "('geopackage','table','column','row','row/col'); "
4507
0
        "END; "
4508
0
        "CREATE TRIGGER 'gpkg_metadata_reference_reference_scope_update' "
4509
0
        "BEFORE UPDATE OF 'reference_scope' ON 'gpkg_metadata_reference' "
4510
0
        "FOR EACH ROW BEGIN "
4511
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4512
0
        "violates constraint: reference_scope must be one of \"geopackage\", "
4513
0
        "\"table\", \"column\", \"row\", \"row/col\"') "
4514
0
        "WHERE NOT NEW.reference_scope IN "
4515
0
        "('geopackage','table','column','row','row/col'); "
4516
0
        "END; "
4517
0
        "CREATE TRIGGER 'gpkg_metadata_reference_column_name_insert' "
4518
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4519
0
        "FOR EACH ROW BEGIN "
4520
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4521
0
        "violates constraint: column name must be NULL when reference_scope "
4522
0
        "is \"geopackage\", \"table\" or \"row\"') "
4523
0
        "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4524
0
        "AND NEW.column_name IS NOT NULL); "
4525
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4526
0
        "violates constraint: column name must be defined for the specified "
4527
0
        "table when reference_scope is \"column\" or \"row/col\"') "
4528
0
        "WHERE (NEW.reference_scope IN ('column','row/col') "
4529
0
        "AND NOT NEW.table_name IN ( "
4530
0
        "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4531
0
        "AND name = NEW.table_name "
4532
0
        "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4533
0
        "END; "
4534
0
        "CREATE TRIGGER 'gpkg_metadata_reference_column_name_update' "
4535
0
        "BEFORE UPDATE OF column_name ON 'gpkg_metadata_reference' "
4536
0
        "FOR EACH ROW BEGIN "
4537
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4538
0
        "violates constraint: column name must be NULL when reference_scope "
4539
0
        "is \"geopackage\", \"table\" or \"row\"') "
4540
0
        "WHERE (NEW.reference_scope IN ('geopackage','table','row') "
4541
0
        "AND NEW.column_name IS NOT NULL); "
4542
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4543
0
        "violates constraint: column name must be defined for the specified "
4544
0
        "table when reference_scope is \"column\" or \"row/col\"') "
4545
0
        "WHERE (NEW.reference_scope IN ('column','row/col') "
4546
0
        "AND NOT NEW.table_name IN ( "
4547
0
        "SELECT name FROM SQLITE_MASTER WHERE type = 'table' "
4548
0
        "AND name = NEW.table_name "
4549
0
        "AND sql LIKE ('%' || NEW.column_name || '%'))); "
4550
0
        "END; "
4551
0
        "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_insert' "
4552
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4553
0
        "FOR EACH ROW BEGIN "
4554
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4555
0
        "violates constraint: row_id_value must be NULL when reference_scope "
4556
0
        "is \"geopackage\", \"table\" or \"column\"') "
4557
0
        "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4558
0
        "AND NEW.row_id_value IS NOT NULL; "
4559
0
        "END; "
4560
0
        "CREATE TRIGGER 'gpkg_metadata_reference_row_id_value_update' "
4561
0
        "BEFORE UPDATE OF 'row_id_value' ON 'gpkg_metadata_reference' "
4562
0
        "FOR EACH ROW BEGIN "
4563
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4564
0
        "violates constraint: row_id_value must be NULL when reference_scope "
4565
0
        "is \"geopackage\", \"table\" or \"column\"') "
4566
0
        "WHERE NEW.reference_scope IN ('geopackage','table','column') "
4567
0
        "AND NEW.row_id_value IS NOT NULL; "
4568
0
        "END; "
4569
0
        "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_insert' "
4570
0
        "BEFORE INSERT ON 'gpkg_metadata_reference' "
4571
0
        "FOR EACH ROW BEGIN "
4572
0
        "SELECT RAISE(ABORT, 'insert on table gpkg_metadata_reference "
4573
0
        "violates constraint: timestamp must be a valid time in ISO 8601 "
4574
0
        "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4575
0
        "WHERE NOT (NEW.timestamp GLOB "
4576
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-"
4577
0
        "5][0-9].[0-9][0-9][0-9]Z' "
4578
0
        "AND strftime('%s',NEW.timestamp) NOT NULL); "
4579
0
        "END; "
4580
0
        "CREATE TRIGGER 'gpkg_metadata_reference_timestamp_update' "
4581
0
        "BEFORE UPDATE OF 'timestamp' ON 'gpkg_metadata_reference' "
4582
0
        "FOR EACH ROW BEGIN "
4583
0
        "SELECT RAISE(ABORT, 'update on table gpkg_metadata_reference "
4584
0
        "violates constraint: timestamp must be a valid time in ISO 8601 "
4585
0
        "\"yyyy-mm-ddThh:mm:ss.cccZ\" form') "
4586
0
        "WHERE NOT (NEW.timestamp GLOB "
4587
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-"
4588
0
        "5][0-9].[0-9][0-9][0-9]Z' "
4589
0
        "AND strftime('%s',NEW.timestamp) NOT NULL); "
4590
0
        "END";
4591
0
    if (bCreateTriggers)
4592
0
    {
4593
0
        osSQL += ";";
4594
0
        osSQL += pszMetadataReferenceTriggers;
4595
0
    }
4596
4597
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
4598
0
        return false;
4599
4600
0
    osSQL += ";";
4601
0
    osSQL += "INSERT INTO gpkg_extensions "
4602
0
             "(table_name, column_name, extension_name, definition, scope) "
4603
0
             "VALUES "
4604
0
             "('gpkg_metadata', NULL, 'gpkg_metadata', "
4605
0
             "'http://www.geopackage.org/spec120/#extension_metadata', "
4606
0
             "'read-write')";
4607
4608
0
    osSQL += ";";
4609
0
    osSQL += "INSERT INTO gpkg_extensions "
4610
0
             "(table_name, column_name, extension_name, definition, scope) "
4611
0
             "VALUES "
4612
0
             "('gpkg_metadata_reference', NULL, 'gpkg_metadata', "
4613
0
             "'http://www.geopackage.org/spec120/#extension_metadata', "
4614
0
             "'read-write')";
4615
4616
0
    const bool bOK = SQLCommand(hDB, osSQL) == OGRERR_NONE;
4617
0
    m_nHasMetadataTables = bOK;
4618
0
    return bOK;
4619
0
}
4620
4621
/************************************************************************/
4622
/*                            FlushMetadata()                           */
4623
/************************************************************************/
4624
4625
void GDALGeoPackageDataset::FlushMetadata()
4626
240k
{
4627
240k
    if (!m_bMetadataDirty || m_poParentDS != nullptr ||
4628
0
        m_nCreateMetadataTables == FALSE)
4629
240k
        return;
4630
0
    m_bMetadataDirty = false;
4631
4632
0
    if (eAccess == GA_ReadOnly)
4633
0
    {
4634
0
        return;
4635
0
    }
4636
4637
0
    bool bCanWriteAreaOrPoint =
4638
0
        !m_bGridCellEncodingAsCO &&
4639
0
        (m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT);
4640
0
    if (!m_osRasterTable.empty())
4641
0
    {
4642
0
        const char *pszIdentifier =
4643
0
            GDALGeoPackageDataset::GetMetadataItem("IDENTIFIER");
4644
0
        const char *pszDescription =
4645
0
            GDALGeoPackageDataset::GetMetadataItem("DESCRIPTION");
4646
0
        if (!m_bIdentifierAsCO && pszIdentifier != nullptr &&
4647
0
            pszIdentifier != m_osIdentifier)
4648
0
        {
4649
0
            m_osIdentifier = pszIdentifier;
4650
0
            char *pszSQL =
4651
0
                sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4652
0
                                "WHERE lower(table_name) = lower('%q')",
4653
0
                                pszIdentifier, m_osRasterTable.c_str());
4654
0
            SQLCommand(hDB, pszSQL);
4655
0
            sqlite3_free(pszSQL);
4656
0
        }
4657
0
        if (!m_bDescriptionAsCO && pszDescription != nullptr &&
4658
0
            pszDescription != m_osDescription)
4659
0
        {
4660
0
            m_osDescription = pszDescription;
4661
0
            char *pszSQL =
4662
0
                sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4663
0
                                "WHERE lower(table_name) = lower('%q')",
4664
0
                                pszDescription, m_osRasterTable.c_str());
4665
0
            SQLCommand(hDB, pszSQL);
4666
0
            sqlite3_free(pszSQL);
4667
0
        }
4668
0
        if (bCanWriteAreaOrPoint)
4669
0
        {
4670
0
            const char *pszAreaOrPoint =
4671
0
                GDALGeoPackageDataset::GetMetadataItem(GDALMD_AREA_OR_POINT);
4672
0
            if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_AREA))
4673
0
            {
4674
0
                bCanWriteAreaOrPoint = false;
4675
0
                char *pszSQL = sqlite3_mprintf(
4676
0
                    "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4677
0
                    "grid_cell_encoding = 'grid-value-is-area' WHERE "
4678
0
                    "lower(tile_matrix_set_name) = lower('%q')",
4679
0
                    m_osRasterTable.c_str());
4680
0
                SQLCommand(hDB, pszSQL);
4681
0
                sqlite3_free(pszSQL);
4682
0
            }
4683
0
            else if (pszAreaOrPoint && EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT))
4684
0
            {
4685
0
                bCanWriteAreaOrPoint = false;
4686
0
                char *pszSQL = sqlite3_mprintf(
4687
0
                    "UPDATE gpkg_2d_gridded_coverage_ancillary SET "
4688
0
                    "grid_cell_encoding = 'grid-value-is-center' WHERE "
4689
0
                    "lower(tile_matrix_set_name) = lower('%q')",
4690
0
                    m_osRasterTable.c_str());
4691
0
                SQLCommand(hDB, pszSQL);
4692
0
                sqlite3_free(pszSQL);
4693
0
            }
4694
0
        }
4695
0
    }
4696
4697
0
    char **papszMDDup = nullptr;
4698
0
    for (char **papszIter = GDALGeoPackageDataset::GetMetadata();
4699
0
         papszIter && *papszIter; ++papszIter)
4700
0
    {
4701
0
        if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4702
0
            continue;
4703
0
        if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4704
0
            continue;
4705
0
        if (STARTS_WITH_CI(*papszIter, "ZOOM_LEVEL="))
4706
0
            continue;
4707
0
        if (STARTS_WITH_CI(*papszIter, "GPKG_METADATA_ITEM_"))
4708
0
            continue;
4709
0
        if ((m_eTF == GPKG_TF_PNG_16BIT || m_eTF == GPKG_TF_TIFF_32BIT_FLOAT) &&
4710
0
            !bCanWriteAreaOrPoint &&
4711
0
            STARTS_WITH_CI(*papszIter, GDALMD_AREA_OR_POINT))
4712
0
        {
4713
0
            continue;
4714
0
        }
4715
0
        papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4716
0
    }
4717
4718
0
    CPLXMLNode *psXMLNode = nullptr;
4719
0
    {
4720
0
        GDALMultiDomainMetadata oLocalMDMD;
4721
0
        CSLConstList papszDomainList = oMDMD.GetDomainList();
4722
0
        CSLConstList papszIter = papszDomainList;
4723
0
        oLocalMDMD.SetMetadata(papszMDDup);
4724
0
        while (papszIter && *papszIter)
4725
0
        {
4726
0
            if (!EQUAL(*papszIter, "") &&
4727
0
                !EQUAL(*papszIter, "IMAGE_STRUCTURE") &&
4728
0
                !EQUAL(*papszIter, "GEOPACKAGE"))
4729
0
            {
4730
0
                oLocalMDMD.SetMetadata(oMDMD.GetMetadata(*papszIter),
4731
0
                                       *papszIter);
4732
0
            }
4733
0
            papszIter++;
4734
0
        }
4735
0
        if (m_nBandCountFromMetadata > 0)
4736
0
        {
4737
0
            oLocalMDMD.SetMetadataItem(
4738
0
                "BAND_COUNT", CPLSPrintf("%d", m_nBandCountFromMetadata),
4739
0
                "IMAGE_STRUCTURE");
4740
0
            if (nBands == 1)
4741
0
            {
4742
0
                const auto poCT = GetRasterBand(1)->GetColorTable();
4743
0
                if (poCT)
4744
0
                {
4745
0
                    std::string osVal("{");
4746
0
                    const int nColorCount = poCT->GetColorEntryCount();
4747
0
                    for (int i = 0; i < nColorCount; ++i)
4748
0
                    {
4749
0
                        if (i > 0)
4750
0
                            osVal += ',';
4751
0
                        const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
4752
0
                        osVal +=
4753
0
                            CPLSPrintf("{%d,%d,%d,%d}", psEntry->c1,
4754
0
                                       psEntry->c2, psEntry->c3, psEntry->c4);
4755
0
                    }
4756
0
                    osVal += '}';
4757
0
                    oLocalMDMD.SetMetadataItem("COLOR_TABLE", osVal.c_str(),
4758
0
                                               "IMAGE_STRUCTURE");
4759
0
                }
4760
0
            }
4761
0
            if (nBands == 1)
4762
0
            {
4763
0
                const char *pszTILE_FORMAT = nullptr;
4764
0
                switch (m_eTF)
4765
0
                {
4766
0
                    case GPKG_TF_PNG_JPEG:
4767
0
                        pszTILE_FORMAT = "JPEG_PNG";
4768
0
                        break;
4769
0
                    case GPKG_TF_PNG:
4770
0
                        break;
4771
0
                    case GPKG_TF_PNG8:
4772
0
                        pszTILE_FORMAT = "PNG8";
4773
0
                        break;
4774
0
                    case GPKG_TF_JPEG:
4775
0
                        pszTILE_FORMAT = "JPEG";
4776
0
                        break;
4777
0
                    case GPKG_TF_WEBP:
4778
0
                        pszTILE_FORMAT = "WEBP";
4779
0
                        break;
4780
0
                    case GPKG_TF_PNG_16BIT:
4781
0
                        break;
4782
0
                    case GPKG_TF_TIFF_32BIT_FLOAT:
4783
0
                        break;
4784
0
                }
4785
0
                if (pszTILE_FORMAT)
4786
0
                    oLocalMDMD.SetMetadataItem("TILE_FORMAT", pszTILE_FORMAT,
4787
0
                                               "IMAGE_STRUCTURE");
4788
0
            }
4789
0
        }
4790
0
        if (GetRasterCount() > 0 &&
4791
0
            GetRasterBand(1)->GetRasterDataType() == GDT_UInt8)
4792
0
        {
4793
0
            int bHasNoData = FALSE;
4794
0
            const double dfNoDataValue =
4795
0
                GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4796
0
            if (bHasNoData)
4797
0
            {
4798
0
                oLocalMDMD.SetMetadataItem("NODATA_VALUE",
4799
0
                                           CPLSPrintf("%.17g", dfNoDataValue),
4800
0
                                           "IMAGE_STRUCTURE");
4801
0
            }
4802
0
        }
4803
0
        for (int i = 1; i <= GetRasterCount(); ++i)
4804
0
        {
4805
0
            auto poBand =
4806
0
                cpl::down_cast<GDALGeoPackageRasterBand *>(GetRasterBand(i));
4807
0
            poBand->AddImplicitStatistics(false);
4808
0
            char **papszMD = GetRasterBand(i)->GetMetadata();
4809
0
            poBand->AddImplicitStatistics(true);
4810
0
            if (papszMD)
4811
0
            {
4812
0
                oLocalMDMD.SetMetadata(papszMD, CPLSPrintf("BAND_%d", i));
4813
0
            }
4814
0
        }
4815
0
        psXMLNode = oLocalMDMD.Serialize();
4816
0
    }
4817
4818
0
    CSLDestroy(papszMDDup);
4819
0
    papszMDDup = nullptr;
4820
4821
0
    WriteMetadata(psXMLNode, m_osRasterTable.c_str());
4822
4823
0
    if (!m_osRasterTable.empty())
4824
0
    {
4825
0
        char **papszGeopackageMD =
4826
0
            GDALGeoPackageDataset::GetMetadata("GEOPACKAGE");
4827
4828
0
        papszMDDup = nullptr;
4829
0
        for (char **papszIter = papszGeopackageMD; papszIter && *papszIter;
4830
0
             ++papszIter)
4831
0
        {
4832
0
            papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4833
0
        }
4834
4835
0
        GDALMultiDomainMetadata oLocalMDMD;
4836
0
        oLocalMDMD.SetMetadata(papszMDDup);
4837
0
        CSLDestroy(papszMDDup);
4838
0
        papszMDDup = nullptr;
4839
0
        psXMLNode = oLocalMDMD.Serialize();
4840
4841
0
        WriteMetadata(psXMLNode, nullptr);
4842
0
    }
4843
4844
0
    for (auto &poLayer : m_apoLayers)
4845
0
    {
4846
0
        const char *pszIdentifier = poLayer->GetMetadataItem("IDENTIFIER");
4847
0
        const char *pszDescription = poLayer->GetMetadataItem("DESCRIPTION");
4848
0
        if (pszIdentifier != nullptr)
4849
0
        {
4850
0
            char *pszSQL =
4851
0
                sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' "
4852
0
                                "WHERE lower(table_name) = lower('%q')",
4853
0
                                pszIdentifier, poLayer->GetName());
4854
0
            SQLCommand(hDB, pszSQL);
4855
0
            sqlite3_free(pszSQL);
4856
0
        }
4857
0
        if (pszDescription != nullptr)
4858
0
        {
4859
0
            char *pszSQL =
4860
0
                sqlite3_mprintf("UPDATE gpkg_contents SET description = '%q' "
4861
0
                                "WHERE lower(table_name) = lower('%q')",
4862
0
                                pszDescription, poLayer->GetName());
4863
0
            SQLCommand(hDB, pszSQL);
4864
0
            sqlite3_free(pszSQL);
4865
0
        }
4866
4867
0
        papszMDDup = nullptr;
4868
0
        for (char **papszIter = poLayer->GetMetadata(); papszIter && *papszIter;
4869
0
             ++papszIter)
4870
0
        {
4871
0
            if (STARTS_WITH_CI(*papszIter, "IDENTIFIER="))
4872
0
                continue;
4873
0
            if (STARTS_WITH_CI(*papszIter, "DESCRIPTION="))
4874
0
                continue;
4875
0
            if (STARTS_WITH_CI(*papszIter, "OLMD_FID64="))
4876
0
                continue;
4877
0
            papszMDDup = CSLInsertString(papszMDDup, -1, *papszIter);
4878
0
        }
4879
4880
0
        {
4881
0
            GDALMultiDomainMetadata oLocalMDMD;
4882
0
            char **papszDomainList = poLayer->GetMetadataDomainList();
4883
0
            char **papszIter = papszDomainList;
4884
0
            oLocalMDMD.SetMetadata(papszMDDup);
4885
0
            while (papszIter && *papszIter)
4886
0
            {
4887
0
                if (!EQUAL(*papszIter, ""))
4888
0
                    oLocalMDMD.SetMetadata(poLayer->GetMetadata(*papszIter),
4889
0
                                           *papszIter);
4890
0
                papszIter++;
4891
0
            }
4892
0
            CSLDestroy(papszDomainList);
4893
0
            psXMLNode = oLocalMDMD.Serialize();
4894
0
        }
4895
4896
0
        CSLDestroy(papszMDDup);
4897
0
        papszMDDup = nullptr;
4898
4899
0
        WriteMetadata(psXMLNode, poLayer->GetName());
4900
0
    }
4901
0
}
4902
4903
/************************************************************************/
4904
/*                          GetMetadataItem()                           */
4905
/************************************************************************/
4906
4907
const char *GDALGeoPackageDataset::GetMetadataItem(const char *pszName,
4908
                                                   const char *pszDomain)
4909
3.89k
{
4910
3.89k
    pszDomain = CheckMetadataDomain(pszDomain);
4911
3.89k
    return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
4912
3.89k
}
4913
4914
/************************************************************************/
4915
/*                            SetMetadata()                             */
4916
/************************************************************************/
4917
4918
CPLErr GDALGeoPackageDataset::SetMetadata(char **papszMetadata,
4919
                                          const char *pszDomain)
4920
0
{
4921
0
    pszDomain = CheckMetadataDomain(pszDomain);
4922
0
    m_bMetadataDirty = true;
4923
0
    GetMetadata(); /* force loading from storage if needed */
4924
0
    return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);
4925
0
}
4926
4927
/************************************************************************/
4928
/*                          SetMetadataItem()                           */
4929
/************************************************************************/
4930
4931
CPLErr GDALGeoPackageDataset::SetMetadataItem(const char *pszName,
4932
                                              const char *pszValue,
4933
                                              const char *pszDomain)
4934
0
{
4935
0
    pszDomain = CheckMetadataDomain(pszDomain);
4936
0
    m_bMetadataDirty = true;
4937
0
    GetMetadata(); /* force loading from storage if needed */
4938
0
    return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);
4939
0
}
4940
4941
/************************************************************************/
4942
/*                                Create()                              */
4943
/************************************************************************/
4944
4945
int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize,
4946
                                  int nYSize, int nBandsIn, GDALDataType eDT,
4947
                                  char **papszOptions)
4948
494
{
4949
494
    CPLString osCommand;
4950
4951
    /* First, ensure there isn't any such file yet. */
4952
494
    VSIStatBufL sStatBuf;
4953
4954
494
    if (nBandsIn != 0)
4955
0
    {
4956
0
        if (eDT == GDT_UInt8)
4957
0
        {
4958
0
            if (nBandsIn != 1 && nBandsIn != 2 && nBandsIn != 3 &&
4959
0
                nBandsIn != 4)
4960
0
            {
4961
0
                CPLError(CE_Failure, CPLE_NotSupported,
4962
0
                         "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), "
4963
0
                         "3 (RGB) or 4 (RGBA) band dataset supported for "
4964
0
                         "Byte datatype");
4965
0
                return FALSE;
4966
0
            }
4967
0
        }
4968
0
        else if (eDT == GDT_Int16 || eDT == GDT_UInt16 || eDT == GDT_Float32)
4969
0
        {
4970
0
            if (nBandsIn != 1)
4971
0
            {
4972
0
                CPLError(CE_Failure, CPLE_NotSupported,
4973
0
                         "Only single band dataset supported for non Byte "
4974
0
                         "datatype");
4975
0
                return FALSE;
4976
0
            }
4977
0
        }
4978
0
        else
4979
0
        {
4980
0
            CPLError(CE_Failure, CPLE_NotSupported,
4981
0
                     "Only Byte, Int16, UInt16 or Float32 supported");
4982
0
            return FALSE;
4983
0
        }
4984
0
    }
4985
4986
494
    const size_t nFilenameLen = strlen(pszFilename);
4987
494
    const bool bGpkgZip =
4988
494
        (nFilenameLen > strlen(".gpkg.zip") &&
4989
494
         !STARTS_WITH(pszFilename, "/vsizip/") &&
4990
494
         EQUAL(pszFilename + nFilenameLen - strlen(".gpkg.zip"), ".gpkg.zip"));
4991
4992
494
    const bool bUseTempFile =
4993
494
        bGpkgZip || (CPLTestBool(CPLGetConfigOption(
4994
96
                         "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "NO")) &&
4995
0
                     (VSIHasOptimizedReadMultiRange(pszFilename) != FALSE ||
4996
0
                      EQUAL(CPLGetConfigOption(
4997
0
                                "CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""),
4998
0
                            "FORCED")));
4999
5000
494
    bool bFileExists = false;
5001
494
    if (VSIStatL(pszFilename, &sStatBuf) == 0)
5002
0
    {
5003
0
        bFileExists = true;
5004
0
        if (nBandsIn == 0 || bUseTempFile ||
5005
0
            !CPLTestBool(
5006
0
                CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")))
5007
0
        {
5008
0
            CPLError(CE_Failure, CPLE_AppDefined,
5009
0
                     "A file system object called '%s' already exists.",
5010
0
                     pszFilename);
5011
5012
0
            return FALSE;
5013
0
        }
5014
0
    }
5015
5016
494
    if (bUseTempFile)
5017
398
    {
5018
398
        if (bGpkgZip)
5019
398
        {
5020
398
            std::string osFilenameInZip(CPLGetFilename(pszFilename));
5021
398
            osFilenameInZip.resize(osFilenameInZip.size() - strlen(".zip"));
5022
398
            m_osFinalFilename =
5023
398
                std::string("/vsizip/{") + pszFilename + "}/" + osFilenameInZip;
5024
398
        }
5025
0
        else
5026
0
        {
5027
0
            m_osFinalFilename = pszFilename;
5028
0
        }
5029
398
        m_pszFilename = CPLStrdup(
5030
398
            CPLGenerateTempFilenameSafe(CPLGetFilename(pszFilename)).c_str());
5031
398
        CPLDebug("GPKG", "Creating temporary file %s", m_pszFilename);
5032
398
    }
5033
96
    else
5034
96
    {
5035
96
        m_pszFilename = CPLStrdup(pszFilename);
5036
96
    }
5037
494
    m_bNew = true;
5038
494
    eAccess = GA_Update;
5039
494
    m_bDateTimeWithTZ =
5040
494
        EQUAL(CSLFetchNameValueDef(papszOptions, "DATETIME_FORMAT", "WITH_TZ"),
5041
494
              "WITH_TZ");
5042
5043
    // for test/debug purposes only. true is the nominal value
5044
494
    m_bPNGSupports2Bands =
5045
494
        CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_2BANDS", "TRUE"));
5046
494
    m_bPNGSupportsCT =
5047
494
        CPLTestBool(CPLGetConfigOption("GPKG_PNG_SUPPORTS_CT", "TRUE"));
5048
5049
494
    if (!OpenOrCreateDB(bFileExists
5050
494
                            ? SQLITE_OPEN_READWRITE
5051
494
                            : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE))
5052
3
        return FALSE;
5053
5054
    /* Default to synchronous=off for performance for new file */
5055
491
    if (!bFileExists &&
5056
491
        CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5057
491
    {
5058
491
        SQLCommand(hDB, "PRAGMA synchronous = OFF");
5059
491
    }
5060
5061
    /* OGR UTF-8 support. If we set the UTF-8 Pragma early on, it */
5062
    /* will be written into the main file and supported henceforth */
5063
491
    SQLCommand(hDB, "PRAGMA encoding = \"UTF-8\"");
5064
5065
491
    if (bFileExists)
5066
0
    {
5067
0
        VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
5068
0
        if (fp)
5069
0
        {
5070
0
            GByte abyHeader[100];
5071
0
            VSIFReadL(abyHeader, 1, sizeof(abyHeader), fp);
5072
0
            VSIFCloseL(fp);
5073
5074
0
            memcpy(&m_nApplicationId, abyHeader + knApplicationIdPos, 4);
5075
0
            m_nApplicationId = CPL_MSBWORD32(m_nApplicationId);
5076
0
            memcpy(&m_nUserVersion, abyHeader + knUserVersionPos, 4);
5077
0
            m_nUserVersion = CPL_MSBWORD32(m_nUserVersion);
5078
5079
0
            if (m_nApplicationId == GP10_APPLICATION_ID)
5080
0
            {
5081
0
                CPLDebug("GPKG", "GeoPackage v1.0");
5082
0
            }
5083
0
            else if (m_nApplicationId == GP11_APPLICATION_ID)
5084
0
            {
5085
0
                CPLDebug("GPKG", "GeoPackage v1.1");
5086
0
            }
5087
0
            else if (m_nApplicationId == GPKG_APPLICATION_ID &&
5088
0
                     m_nUserVersion >= GPKG_1_2_VERSION)
5089
0
            {
5090
0
                CPLDebug("GPKG", "GeoPackage v%d.%d.%d", m_nUserVersion / 10000,
5091
0
                         (m_nUserVersion % 10000) / 100, m_nUserVersion % 100);
5092
0
            }
5093
0
        }
5094
5095
0
        DetectSpatialRefSysColumns();
5096
0
    }
5097
5098
491
    const char *pszVersion = CSLFetchNameValue(papszOptions, "VERSION");
5099
491
    if (pszVersion && !EQUAL(pszVersion, "AUTO"))
5100
0
    {
5101
0
        if (EQUAL(pszVersion, "1.0"))
5102
0
        {
5103
0
            m_nApplicationId = GP10_APPLICATION_ID;
5104
0
            m_nUserVersion = 0;
5105
0
        }
5106
0
        else if (EQUAL(pszVersion, "1.1"))
5107
0
        {
5108
0
            m_nApplicationId = GP11_APPLICATION_ID;
5109
0
            m_nUserVersion = 0;
5110
0
        }
5111
0
        else if (EQUAL(pszVersion, "1.2"))
5112
0
        {
5113
0
            m_nApplicationId = GPKG_APPLICATION_ID;
5114
0
            m_nUserVersion = GPKG_1_2_VERSION;
5115
0
        }
5116
0
        else if (EQUAL(pszVersion, "1.3"))
5117
0
        {
5118
0
            m_nApplicationId = GPKG_APPLICATION_ID;
5119
0
            m_nUserVersion = GPKG_1_3_VERSION;
5120
0
        }
5121
0
        else if (EQUAL(pszVersion, "1.4"))
5122
0
        {
5123
0
            m_nApplicationId = GPKG_APPLICATION_ID;
5124
0
            m_nUserVersion = GPKG_1_4_VERSION;
5125
0
        }
5126
0
    }
5127
5128
491
    SoftStartTransaction();
5129
5130
491
    CPLString osSQL;
5131
491
    if (!bFileExists)
5132
491
    {
5133
        /* Requirement 10: A GeoPackage SHALL include a gpkg_spatial_ref_sys
5134
         * table */
5135
        /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5136
491
        osSQL = "CREATE TABLE gpkg_spatial_ref_sys ("
5137
491
                "srs_name TEXT NOT NULL,"
5138
491
                "srs_id INTEGER NOT NULL PRIMARY KEY,"
5139
491
                "organization TEXT NOT NULL,"
5140
491
                "organization_coordsys_id INTEGER NOT NULL,"
5141
491
                "definition  TEXT NOT NULL,"
5142
491
                "description TEXT";
5143
491
        if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "CRS_WKT_EXTENSION",
5144
491
                                             "NO")) ||
5145
491
            (nBandsIn != 0 && eDT != GDT_UInt8))
5146
0
        {
5147
0
            m_bHasDefinition12_063 = true;
5148
0
            osSQL += ", definition_12_063 TEXT NOT NULL";
5149
0
            if (m_nUserVersion >= GPKG_1_4_VERSION)
5150
0
            {
5151
0
                osSQL += ", epoch DOUBLE";
5152
0
                m_bHasEpochColumn = true;
5153
0
            }
5154
0
        }
5155
491
        osSQL += ")"
5156
491
                 ";"
5157
                 /* Requirement 11: The gpkg_spatial_ref_sys table in a
5158
                    GeoPackage SHALL */
5159
                 /* contain a record for EPSG:4326, the geodetic WGS84 SRS */
5160
                 /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5161
5162
491
                 "INSERT INTO gpkg_spatial_ref_sys ("
5163
491
                 "srs_name, srs_id, organization, organization_coordsys_id, "
5164
491
                 "definition, description";
5165
491
        if (m_bHasDefinition12_063)
5166
0
            osSQL += ", definition_12_063";
5167
491
        osSQL +=
5168
491
            ") VALUES ("
5169
491
            "'WGS 84 geodetic', 4326, 'EPSG', 4326, '"
5170
491
            "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
5171
491
            "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
5172
491
            "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
5173
491
            "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
5174
491
            "\"EPSG\",\"9122\"]],AXIS[\"Latitude\",NORTH],AXIS[\"Longitude\","
5175
491
            "EAST],AUTHORITY[\"EPSG\",\"4326\"]]"
5176
491
            "', 'longitude/latitude coordinates in decimal degrees on the WGS "
5177
491
            "84 spheroid'";
5178
491
        if (m_bHasDefinition12_063)
5179
0
            osSQL +=
5180
0
                ", 'GEODCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", "
5181
0
                "ELLIPSOID[\"WGS 84\",6378137, 298.257223563, "
5182
0
                "LENGTHUNIT[\"metre\", 1.0]]], PRIMEM[\"Greenwich\", 0.0, "
5183
0
                "ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal, "
5184
0
                "2], AXIS[\"latitude\", north, ORDER[1]], AXIS[\"longitude\", "
5185
0
                "east, ORDER[2]], ANGLEUNIT[\"degree\", 0.0174532925199433], "
5186
0
                "ID[\"EPSG\", 4326]]'";
5187
491
        osSQL +=
5188
491
            ")"
5189
491
            ";"
5190
            /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5191
               SHALL */
5192
            /* contain a record with an srs_id of -1, an organization of “NONE”,
5193
             */
5194
            /* an organization_coordsys_id of -1, and definition “undefined” */
5195
            /* for undefined Cartesian coordinate reference systems */
5196
            /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5197
491
            "INSERT INTO gpkg_spatial_ref_sys ("
5198
491
            "srs_name, srs_id, organization, organization_coordsys_id, "
5199
491
            "definition, description";
5200
491
        if (m_bHasDefinition12_063)
5201
0
            osSQL += ", definition_12_063";
5202
491
        osSQL += ") VALUES ("
5203
491
                 "'Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', "
5204
491
                 "'undefined Cartesian coordinate reference system'";
5205
491
        if (m_bHasDefinition12_063)
5206
0
            osSQL += ", 'undefined'";
5207
491
        osSQL +=
5208
491
            ")"
5209
491
            ";"
5210
            /* Requirement 11: The gpkg_spatial_ref_sys table in a GeoPackage
5211
               SHALL */
5212
            /* contain a record with an srs_id of 0, an organization of “NONE”,
5213
             */
5214
            /* an organization_coordsys_id of 0, and definition “undefined” */
5215
            /* for undefined geographic coordinate reference systems */
5216
            /* http://opengis.github.io/geopackage/#spatial_ref_sys */
5217
491
            "INSERT INTO gpkg_spatial_ref_sys ("
5218
491
            "srs_name, srs_id, organization, organization_coordsys_id, "
5219
491
            "definition, description";
5220
491
        if (m_bHasDefinition12_063)
5221
0
            osSQL += ", definition_12_063";
5222
491
        osSQL += ") VALUES ("
5223
491
                 "'Undefined geographic SRS', 0, 'NONE', 0, 'undefined', "
5224
491
                 "'undefined geographic coordinate reference system'";
5225
491
        if (m_bHasDefinition12_063)
5226
0
            osSQL += ", 'undefined'";
5227
491
        osSQL += ")"
5228
491
                 ";"
5229
                 /* Requirement 13: A GeoPackage file SHALL include a
5230
                    gpkg_contents table */
5231
                 /* http://opengis.github.io/geopackage/#_contents */
5232
491
                 "CREATE TABLE gpkg_contents ("
5233
491
                 "table_name TEXT NOT NULL PRIMARY KEY,"
5234
491
                 "data_type TEXT NOT NULL,"
5235
491
                 "identifier TEXT UNIQUE,"
5236
491
                 "description TEXT DEFAULT '',"
5237
491
                 "last_change DATETIME NOT NULL DEFAULT "
5238
491
                 "(strftime('%Y-%m-%dT%H:%M:%fZ','now')),"
5239
491
                 "min_x DOUBLE, min_y DOUBLE,"
5240
491
                 "max_x DOUBLE, max_y DOUBLE,"
5241
491
                 "srs_id INTEGER,"
5242
491
                 "CONSTRAINT fk_gc_r_srs_id FOREIGN KEY (srs_id) REFERENCES "
5243
491
                 "gpkg_spatial_ref_sys(srs_id)"
5244
491
                 ")";
5245
5246
491
#ifdef ENABLE_GPKG_OGR_CONTENTS
5247
491
        if (CPLFetchBool(papszOptions, "ADD_GPKG_OGR_CONTENTS", true))
5248
491
        {
5249
491
            m_bHasGPKGOGRContents = true;
5250
491
            osSQL += ";"
5251
491
                     "CREATE TABLE gpkg_ogr_contents("
5252
491
                     "table_name TEXT NOT NULL PRIMARY KEY,"
5253
491
                     "feature_count INTEGER DEFAULT NULL"
5254
491
                     ")";
5255
491
        }
5256
491
#endif
5257
5258
        /* Requirement 21: A GeoPackage with a gpkg_contents table row with a
5259
         * “features” */
5260
        /* data_type SHALL contain a gpkg_geometry_columns table or updateable
5261
         * view */
5262
        /* http://opengis.github.io/geopackage/#_geometry_columns */
5263
491
        const bool bCreateGeometryColumns =
5264
491
            CPLTestBool(CPLGetConfigOption("CREATE_GEOMETRY_COLUMNS", "YES"));
5265
491
        if (bCreateGeometryColumns)
5266
491
        {
5267
491
            m_bHasGPKGGeometryColumns = true;
5268
491
            osSQL += ";";
5269
491
            osSQL += pszCREATE_GPKG_GEOMETRY_COLUMNS;
5270
491
        }
5271
491
    }
5272
5273
491
    const bool bCreateTriggers =
5274
491
        CPLTestBool(CPLGetConfigOption("CREATE_TRIGGERS", "YES"));
5275
491
    if ((bFileExists && nBandsIn != 0 &&
5276
0
         SQLGetInteger(
5277
0
             hDB,
5278
0
             "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_tile_matrix_set' "
5279
0
             "AND type in ('table', 'view')",
5280
0
             nullptr) == 0) ||
5281
491
        (!bFileExists &&
5282
491
         CPLTestBool(CPLGetConfigOption("CREATE_RASTER_TABLES", "YES"))))
5283
491
    {
5284
491
        if (!osSQL.empty())
5285
491
            osSQL += ";";
5286
5287
        /* From C.5. gpkg_tile_matrix_set Table 28. gpkg_tile_matrix_set Table
5288
         * Creation SQL  */
5289
491
        osSQL += "CREATE TABLE gpkg_tile_matrix_set ("
5290
491
                 "table_name TEXT NOT NULL PRIMARY KEY,"
5291
491
                 "srs_id INTEGER NOT NULL,"
5292
491
                 "min_x DOUBLE NOT NULL,"
5293
491
                 "min_y DOUBLE NOT NULL,"
5294
491
                 "max_x DOUBLE NOT NULL,"
5295
491
                 "max_y DOUBLE NOT NULL,"
5296
491
                 "CONSTRAINT fk_gtms_table_name FOREIGN KEY (table_name) "
5297
491
                 "REFERENCES gpkg_contents(table_name),"
5298
491
                 "CONSTRAINT fk_gtms_srs FOREIGN KEY (srs_id) REFERENCES "
5299
491
                 "gpkg_spatial_ref_sys (srs_id)"
5300
491
                 ")"
5301
491
                 ";"
5302
5303
                 /* From C.6. gpkg_tile_matrix Table 29. gpkg_tile_matrix Table
5304
                    Creation SQL */
5305
491
                 "CREATE TABLE gpkg_tile_matrix ("
5306
491
                 "table_name TEXT NOT NULL,"
5307
491
                 "zoom_level INTEGER NOT NULL,"
5308
491
                 "matrix_width INTEGER NOT NULL,"
5309
491
                 "matrix_height INTEGER NOT NULL,"
5310
491
                 "tile_width INTEGER NOT NULL,"
5311
491
                 "tile_height INTEGER NOT NULL,"
5312
491
                 "pixel_x_size DOUBLE NOT NULL,"
5313
491
                 "pixel_y_size DOUBLE NOT NULL,"
5314
491
                 "CONSTRAINT pk_ttm PRIMARY KEY (table_name, zoom_level),"
5315
491
                 "CONSTRAINT fk_tmm_table_name FOREIGN KEY (table_name) "
5316
491
                 "REFERENCES gpkg_contents(table_name)"
5317
491
                 ")";
5318
5319
491
        if (bCreateTriggers)
5320
491
        {
5321
            /* From D.1. gpkg_tile_matrix Table 39. gpkg_tile_matrix Trigger
5322
             * Definition SQL */
5323
491
            const char *pszTileMatrixTrigger =
5324
491
                "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_insert' "
5325
491
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5326
491
                "FOR EACH ROW BEGIN "
5327
491
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5328
491
                "violates constraint: zoom_level cannot be less than 0') "
5329
491
                "WHERE (NEW.zoom_level < 0); "
5330
491
                "END; "
5331
491
                "CREATE TRIGGER 'gpkg_tile_matrix_zoom_level_update' "
5332
491
                "BEFORE UPDATE of zoom_level ON 'gpkg_tile_matrix' "
5333
491
                "FOR EACH ROW BEGIN "
5334
491
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5335
491
                "violates constraint: zoom_level cannot be less than 0') "
5336
491
                "WHERE (NEW.zoom_level < 0); "
5337
491
                "END; "
5338
491
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_insert' "
5339
491
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5340
491
                "FOR EACH ROW BEGIN "
5341
491
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5342
491
                "violates constraint: matrix_width cannot be less than 1') "
5343
491
                "WHERE (NEW.matrix_width < 1); "
5344
491
                "END; "
5345
491
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_width_update' "
5346
491
                "BEFORE UPDATE OF matrix_width ON 'gpkg_tile_matrix' "
5347
491
                "FOR EACH ROW BEGIN "
5348
491
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5349
491
                "violates constraint: matrix_width cannot be less than 1') "
5350
491
                "WHERE (NEW.matrix_width < 1); "
5351
491
                "END; "
5352
491
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_insert' "
5353
491
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5354
491
                "FOR EACH ROW BEGIN "
5355
491
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5356
491
                "violates constraint: matrix_height cannot be less than 1') "
5357
491
                "WHERE (NEW.matrix_height < 1); "
5358
491
                "END; "
5359
491
                "CREATE TRIGGER 'gpkg_tile_matrix_matrix_height_update' "
5360
491
                "BEFORE UPDATE OF matrix_height ON 'gpkg_tile_matrix' "
5361
491
                "FOR EACH ROW BEGIN "
5362
491
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5363
491
                "violates constraint: matrix_height cannot be less than 1') "
5364
491
                "WHERE (NEW.matrix_height < 1); "
5365
491
                "END; "
5366
491
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_insert' "
5367
491
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5368
491
                "FOR EACH ROW BEGIN "
5369
491
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5370
491
                "violates constraint: pixel_x_size must be greater than 0') "
5371
491
                "WHERE NOT (NEW.pixel_x_size > 0); "
5372
491
                "END; "
5373
491
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_x_size_update' "
5374
491
                "BEFORE UPDATE OF pixel_x_size ON 'gpkg_tile_matrix' "
5375
491
                "FOR EACH ROW BEGIN "
5376
491
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5377
491
                "violates constraint: pixel_x_size must be greater than 0') "
5378
491
                "WHERE NOT (NEW.pixel_x_size > 0); "
5379
491
                "END; "
5380
491
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_insert' "
5381
491
                "BEFORE INSERT ON 'gpkg_tile_matrix' "
5382
491
                "FOR EACH ROW BEGIN "
5383
491
                "SELECT RAISE(ABORT, 'insert on table ''gpkg_tile_matrix'' "
5384
491
                "violates constraint: pixel_y_size must be greater than 0') "
5385
491
                "WHERE NOT (NEW.pixel_y_size > 0); "
5386
491
                "END; "
5387
491
                "CREATE TRIGGER 'gpkg_tile_matrix_pixel_y_size_update' "
5388
491
                "BEFORE UPDATE OF pixel_y_size ON 'gpkg_tile_matrix' "
5389
491
                "FOR EACH ROW BEGIN "
5390
491
                "SELECT RAISE(ABORT, 'update on table ''gpkg_tile_matrix'' "
5391
491
                "violates constraint: pixel_y_size must be greater than 0') "
5392
491
                "WHERE NOT (NEW.pixel_y_size > 0); "
5393
491
                "END;";
5394
491
            osSQL += ";";
5395
491
            osSQL += pszTileMatrixTrigger;
5396
491
        }
5397
491
    }
5398
5399
491
    if (!osSQL.empty() && OGRERR_NONE != SQLCommand(hDB, osSQL))
5400
1
        return FALSE;
5401
5402
490
    if (!bFileExists)
5403
490
    {
5404
490
        const char *pszMetadataTables =
5405
490
            CSLFetchNameValue(papszOptions, "METADATA_TABLES");
5406
490
        if (pszMetadataTables)
5407
0
            m_nCreateMetadataTables = int(CPLTestBool(pszMetadataTables));
5408
5409
490
        if (m_nCreateMetadataTables == TRUE && !CreateMetadataTables())
5410
0
            return FALSE;
5411
5412
490
        if (m_bHasDefinition12_063)
5413
0
        {
5414
0
            if (OGRERR_NONE != CreateExtensionsTableIfNecessary() ||
5415
0
                OGRERR_NONE !=
5416
0
                    SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5417
0
                                    "(table_name, column_name, extension_name, "
5418
0
                                    "definition, scope) "
5419
0
                                    "VALUES "
5420
0
                                    "('gpkg_spatial_ref_sys', "
5421
0
                                    "'definition_12_063', 'gpkg_crs_wkt', "
5422
0
                                    "'http://www.geopackage.org/spec120/"
5423
0
                                    "#extension_crs_wkt', 'read-write')"))
5424
0
            {
5425
0
                return FALSE;
5426
0
            }
5427
0
            if (m_bHasEpochColumn)
5428
0
            {
5429
0
                if (OGRERR_NONE !=
5430
0
                        SQLCommand(
5431
0
                            hDB, "UPDATE gpkg_extensions SET extension_name = "
5432
0
                                 "'gpkg_crs_wkt_1_1' "
5433
0
                                 "WHERE extension_name = 'gpkg_crs_wkt'") ||
5434
0
                    OGRERR_NONE !=
5435
0
                        SQLCommand(hDB, "INSERT INTO gpkg_extensions "
5436
0
                                        "(table_name, column_name, "
5437
0
                                        "extension_name, definition, scope) "
5438
0
                                        "VALUES "
5439
0
                                        "('gpkg_spatial_ref_sys', 'epoch', "
5440
0
                                        "'gpkg_crs_wkt_1_1', "
5441
0
                                        "'http://www.geopackage.org/spec/"
5442
0
                                        "#extension_crs_wkt', "
5443
0
                                        "'read-write')"))
5444
0
                {
5445
0
                    return FALSE;
5446
0
                }
5447
0
            }
5448
0
        }
5449
490
    }
5450
5451
490
    if (nBandsIn != 0)
5452
0
    {
5453
0
        const std::string osTableName = CPLGetBasenameSafe(m_pszFilename);
5454
0
        m_osRasterTable = CSLFetchNameValueDef(papszOptions, "RASTER_TABLE",
5455
0
                                               osTableName.c_str());
5456
0
        if (m_osRasterTable.empty())
5457
0
        {
5458
0
            CPLError(CE_Failure, CPLE_AppDefined,
5459
0
                     "RASTER_TABLE must be set to a non empty value");
5460
0
            return FALSE;
5461
0
        }
5462
0
        m_bIdentifierAsCO =
5463
0
            CSLFetchNameValue(papszOptions, "RASTER_IDENTIFIER") != nullptr;
5464
0
        m_osIdentifier = CSLFetchNameValueDef(papszOptions, "RASTER_IDENTIFIER",
5465
0
                                              m_osRasterTable);
5466
0
        m_bDescriptionAsCO =
5467
0
            CSLFetchNameValue(papszOptions, "RASTER_DESCRIPTION") != nullptr;
5468
0
        m_osDescription =
5469
0
            CSLFetchNameValueDef(papszOptions, "RASTER_DESCRIPTION", "");
5470
0
        SetDataType(eDT);
5471
0
        if (eDT == GDT_Int16)
5472
0
            SetGlobalOffsetScale(-32768.0, 1.0);
5473
5474
        /* From C.7. sample_tile_pyramid (Informative) Table 31. EXAMPLE: tiles
5475
         * table Create Table SQL (Informative) */
5476
0
        char *pszSQL =
5477
0
            sqlite3_mprintf("CREATE TABLE \"%w\" ("
5478
0
                            "id INTEGER PRIMARY KEY AUTOINCREMENT,"
5479
0
                            "zoom_level INTEGER NOT NULL,"
5480
0
                            "tile_column INTEGER NOT NULL,"
5481
0
                            "tile_row INTEGER NOT NULL,"
5482
0
                            "tile_data BLOB NOT NULL,"
5483
0
                            "UNIQUE (zoom_level, tile_column, tile_row)"
5484
0
                            ")",
5485
0
                            m_osRasterTable.c_str());
5486
0
        osSQL = pszSQL;
5487
0
        sqlite3_free(pszSQL);
5488
5489
0
        if (bCreateTriggers)
5490
0
        {
5491
0
            osSQL += ";";
5492
0
            osSQL += CreateRasterTriggersSQL(m_osRasterTable);
5493
0
        }
5494
5495
0
        OGRErr eErr = SQLCommand(hDB, osSQL);
5496
0
        if (OGRERR_NONE != eErr)
5497
0
            return FALSE;
5498
5499
0
        const char *pszTF = CSLFetchNameValue(papszOptions, "TILE_FORMAT");
5500
0
        if (eDT == GDT_Int16 || eDT == GDT_UInt16)
5501
0
        {
5502
0
            m_eTF = GPKG_TF_PNG_16BIT;
5503
0
            if (pszTF)
5504
0
            {
5505
0
                if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "PNG"))
5506
0
                {
5507
0
                    CPLError(CE_Warning, CPLE_NotSupported,
5508
0
                             "Only AUTO or PNG supported "
5509
0
                             "as tile format for Int16 / UInt16");
5510
0
                }
5511
0
            }
5512
0
        }
5513
0
        else if (eDT == GDT_Float32)
5514
0
        {
5515
0
            m_eTF = GPKG_TF_TIFF_32BIT_FLOAT;
5516
0
            if (pszTF)
5517
0
            {
5518
0
                if (EQUAL(pszTF, "PNG"))
5519
0
                    m_eTF = GPKG_TF_PNG_16BIT;
5520
0
                else if (!EQUAL(pszTF, "AUTO") && !EQUAL(pszTF, "TIFF"))
5521
0
                {
5522
0
                    CPLError(CE_Warning, CPLE_NotSupported,
5523
0
                             "Only AUTO, PNG or TIFF supported "
5524
0
                             "as tile format for Float32");
5525
0
                }
5526
0
            }
5527
0
        }
5528
0
        else
5529
0
        {
5530
0
            if (pszTF)
5531
0
            {
5532
0
                m_eTF = GDALGPKGMBTilesGetTileFormat(pszTF);
5533
0
                if (nBandsIn == 1 && m_eTF != GPKG_TF_PNG)
5534
0
                    m_bMetadataDirty = true;
5535
0
            }
5536
0
            else if (nBandsIn == 1)
5537
0
                m_eTF = GPKG_TF_PNG;
5538
0
        }
5539
5540
0
        if (eDT != GDT_UInt8)
5541
0
        {
5542
0
            if (!CreateTileGriddedTable(papszOptions))
5543
0
                return FALSE;
5544
0
        }
5545
5546
0
        nRasterXSize = nXSize;
5547
0
        nRasterYSize = nYSize;
5548
5549
0
        const char *pszTileSize =
5550
0
            CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "256");
5551
0
        const char *pszTileWidth =
5552
0
            CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", pszTileSize);
5553
0
        const char *pszTileHeight =
5554
0
            CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", pszTileSize);
5555
0
        int nTileWidth = atoi(pszTileWidth);
5556
0
        int nTileHeight = atoi(pszTileHeight);
5557
0
        if ((nTileWidth < 8 || nTileWidth > 4096 || nTileHeight < 8 ||
5558
0
             nTileHeight > 4096) &&
5559
0
            !CPLTestBool(CPLGetConfigOption("GPKG_ALLOW_CRAZY_SETTINGS", "NO")))
5560
0
        {
5561
0
            CPLError(CE_Failure, CPLE_AppDefined,
5562
0
                     "Invalid block dimensions: %dx%d", nTileWidth,
5563
0
                     nTileHeight);
5564
0
            return FALSE;
5565
0
        }
5566
5567
0
        for (int i = 1; i <= nBandsIn; i++)
5568
0
        {
5569
0
            SetBand(i, std::make_unique<GDALGeoPackageRasterBand>(
5570
0
                           this, nTileWidth, nTileHeight));
5571
0
        }
5572
5573
0
        GDALPamDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
5574
0
                                        "IMAGE_STRUCTURE");
5575
0
        GDALPamDataset::SetMetadataItem("IDENTIFIER", m_osIdentifier);
5576
0
        if (!m_osDescription.empty())
5577
0
            GDALPamDataset::SetMetadataItem("DESCRIPTION", m_osDescription);
5578
5579
0
        ParseCompressionOptions(papszOptions);
5580
5581
0
        if (m_eTF == GPKG_TF_WEBP)
5582
0
        {
5583
0
            if (!RegisterWebPExtension())
5584
0
                return FALSE;
5585
0
        }
5586
5587
0
        m_osTilingScheme =
5588
0
            CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5589
0
        if (!EQUAL(m_osTilingScheme, "CUSTOM"))
5590
0
        {
5591
0
            const auto poTS = GetTilingScheme(m_osTilingScheme);
5592
0
            if (!poTS)
5593
0
                return FALSE;
5594
5595
0
            if (nTileWidth != poTS->nTileWidth ||
5596
0
                nTileHeight != poTS->nTileHeight)
5597
0
            {
5598
0
                CPLError(CE_Failure, CPLE_NotSupported,
5599
0
                         "Tile dimension should be %dx%d for %s tiling scheme",
5600
0
                         poTS->nTileWidth, poTS->nTileHeight,
5601
0
                         m_osTilingScheme.c_str());
5602
0
                return FALSE;
5603
0
            }
5604
5605
0
            const char *pszZoomLevel =
5606
0
                CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
5607
0
            if (pszZoomLevel)
5608
0
            {
5609
0
                m_nZoomLevel = atoi(pszZoomLevel);
5610
0
                int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
5611
0
                while ((1 << nMaxZoomLevelForThisTM) >
5612
0
                           INT_MAX / poTS->nTileXCountZoomLevel0 ||
5613
0
                       (1 << nMaxZoomLevelForThisTM) >
5614
0
                           INT_MAX / poTS->nTileYCountZoomLevel0)
5615
0
                {
5616
0
                    --nMaxZoomLevelForThisTM;
5617
0
                }
5618
5619
0
                if (m_nZoomLevel < 0 || m_nZoomLevel > nMaxZoomLevelForThisTM)
5620
0
                {
5621
0
                    CPLError(CE_Failure, CPLE_AppDefined,
5622
0
                             "ZOOM_LEVEL = %s is invalid. It should be in "
5623
0
                             "[0,%d] range",
5624
0
                             pszZoomLevel, nMaxZoomLevelForThisTM);
5625
0
                    return FALSE;
5626
0
                }
5627
0
            }
5628
5629
            // Implicitly sets SRS.
5630
0
            OGRSpatialReference oSRS;
5631
0
            if (oSRS.importFromEPSG(poTS->nEPSGCode) != OGRERR_NONE)
5632
0
                return FALSE;
5633
0
            char *pszWKT = nullptr;
5634
0
            oSRS.exportToWkt(&pszWKT);
5635
0
            SetProjection(pszWKT);
5636
0
            CPLFree(pszWKT);
5637
0
        }
5638
0
        else
5639
0
        {
5640
0
            if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5641
0
            {
5642
0
                CPLError(
5643
0
                    CE_Failure, CPLE_NotSupported,
5644
0
                    "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5645
0
                return false;
5646
0
            }
5647
0
        }
5648
0
    }
5649
5650
490
    if (bFileExists && nBandsIn > 0 && eDT == GDT_UInt8)
5651
0
    {
5652
        // If there was an ogr_empty_table table, we can remove it
5653
0
        RemoveOGREmptyTable();
5654
0
    }
5655
5656
490
    SoftCommitTransaction();
5657
5658
    /* Requirement 2 */
5659
    /* We have to do this after there's some content so the database file */
5660
    /* is not zero length */
5661
490
    SetApplicationAndUserVersionId();
5662
5663
    /* Default to synchronous=off for performance for new file */
5664
490
    if (!bFileExists &&
5665
490
        CPLGetConfigOption("OGR_SQLITE_SYNCHRONOUS", nullptr) == nullptr)
5666
490
    {
5667
490
        SQLCommand(hDB, "PRAGMA synchronous = OFF");
5668
490
    }
5669
5670
490
    return TRUE;
5671
490
}
5672
5673
/************************************************************************/
5674
/*                        RemoveOGREmptyTable()                         */
5675
/************************************************************************/
5676
5677
void GDALGeoPackageDataset::RemoveOGREmptyTable()
5678
483
{
5679
    // Run with sqlite3_exec since we don't want errors to be emitted
5680
483
    sqlite3_exec(hDB, "DROP TABLE IF EXISTS ogr_empty_table", nullptr, nullptr,
5681
483
                 nullptr);
5682
483
    sqlite3_exec(
5683
483
        hDB, "DELETE FROM gpkg_contents WHERE table_name = 'ogr_empty_table'",
5684
483
        nullptr, nullptr, nullptr);
5685
483
#ifdef ENABLE_GPKG_OGR_CONTENTS
5686
483
    if (m_bHasGPKGOGRContents)
5687
483
    {
5688
483
        sqlite3_exec(hDB,
5689
483
                     "DELETE FROM gpkg_ogr_contents WHERE "
5690
483
                     "table_name = 'ogr_empty_table'",
5691
483
                     nullptr, nullptr, nullptr);
5692
483
    }
5693
483
#endif
5694
483
    sqlite3_exec(hDB,
5695
483
                 "DELETE FROM gpkg_geometry_columns WHERE "
5696
483
                 "table_name = 'ogr_empty_table'",
5697
483
                 nullptr, nullptr, nullptr);
5698
483
}
5699
5700
/************************************************************************/
5701
/*                        CreateTileGriddedTable()                      */
5702
/************************************************************************/
5703
5704
bool GDALGeoPackageDataset::CreateTileGriddedTable(char **papszOptions)
5705
0
{
5706
0
    CPLString osSQL;
5707
0
    if (!HasGriddedCoverageAncillaryTable())
5708
0
    {
5709
        // It doesn't exist. So create gpkg_extensions table if necessary, and
5710
        // gpkg_2d_gridded_coverage_ancillary & gpkg_2d_gridded_tile_ancillary,
5711
        // and register them as extensions.
5712
0
        if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
5713
0
            return false;
5714
5715
        // Req 1 /table-defs/coverage-ancillary
5716
0
        osSQL = "CREATE TABLE gpkg_2d_gridded_coverage_ancillary ("
5717
0
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5718
0
                "tile_matrix_set_name TEXT NOT NULL UNIQUE,"
5719
0
                "datatype TEXT NOT NULL DEFAULT 'integer',"
5720
0
                "scale REAL NOT NULL DEFAULT 1.0,"
5721
0
                "offset REAL NOT NULL DEFAULT 0.0,"
5722
0
                "precision REAL DEFAULT 1.0,"
5723
0
                "data_null REAL,"
5724
0
                "grid_cell_encoding TEXT DEFAULT 'grid-value-is-center',"
5725
0
                "uom TEXT,"
5726
0
                "field_name TEXT DEFAULT 'Height',"
5727
0
                "quantity_definition TEXT DEFAULT 'Height',"
5728
0
                "CONSTRAINT fk_g2dgtct_name FOREIGN KEY(tile_matrix_set_name) "
5729
0
                "REFERENCES gpkg_tile_matrix_set ( table_name ) "
5730
0
                "CHECK (datatype in ('integer','float')))"
5731
0
                ";"
5732
                // Requirement 2 /table-defs/tile-ancillary
5733
0
                "CREATE TABLE gpkg_2d_gridded_tile_ancillary ("
5734
0
                "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
5735
0
                "tpudt_name TEXT NOT NULL,"
5736
0
                "tpudt_id INTEGER NOT NULL,"
5737
0
                "scale REAL NOT NULL DEFAULT 1.0,"
5738
0
                "offset REAL NOT NULL DEFAULT 0.0,"
5739
0
                "min REAL DEFAULT NULL,"
5740
0
                "max REAL DEFAULT NULL,"
5741
0
                "mean REAL DEFAULT NULL,"
5742
0
                "std_dev REAL DEFAULT NULL,"
5743
0
                "CONSTRAINT fk_g2dgtat_name FOREIGN KEY (tpudt_name) "
5744
0
                "REFERENCES gpkg_contents(table_name),"
5745
0
                "UNIQUE (tpudt_name, tpudt_id))"
5746
0
                ";"
5747
                // Requirement 6 /gpkg-extensions
5748
0
                "INSERT INTO gpkg_extensions "
5749
0
                "(table_name, column_name, extension_name, definition, scope) "
5750
0
                "VALUES ('gpkg_2d_gridded_coverage_ancillary', NULL, "
5751
0
                "'gpkg_2d_gridded_coverage', "
5752
0
                "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5753
0
                "'read-write')"
5754
0
                ";"
5755
                // Requirement 6 /gpkg-extensions
5756
0
                "INSERT INTO gpkg_extensions "
5757
0
                "(table_name, column_name, extension_name, definition, scope) "
5758
0
                "VALUES ('gpkg_2d_gridded_tile_ancillary', NULL, "
5759
0
                "'gpkg_2d_gridded_coverage', "
5760
0
                "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5761
0
                "'read-write')"
5762
0
                ";";
5763
0
    }
5764
5765
    // Requirement 6 /gpkg-extensions
5766
0
    char *pszSQL = sqlite3_mprintf(
5767
0
        "INSERT INTO gpkg_extensions "
5768
0
        "(table_name, column_name, extension_name, definition, scope) "
5769
0
        "VALUES ('%q', 'tile_data', "
5770
0
        "'gpkg_2d_gridded_coverage', "
5771
0
        "'http://docs.opengeospatial.org/is/17-066r1/17-066r1.html', "
5772
0
        "'read-write')",
5773
0
        m_osRasterTable.c_str());
5774
0
    osSQL += pszSQL;
5775
0
    osSQL += ";";
5776
0
    sqlite3_free(pszSQL);
5777
5778
    // Requirement 7 /gpkg-2d-gridded-coverage-ancillary
5779
    // Requirement 8 /gpkg-2d-gridded-coverage-ancillary-set-name
5780
    // Requirement 9 /gpkg-2d-gridded-coverage-ancillary-datatype
5781
0
    m_dfPrecision =
5782
0
        CPLAtof(CSLFetchNameValueDef(papszOptions, "PRECISION", "1"));
5783
0
    CPLString osGridCellEncoding(CSLFetchNameValueDef(
5784
0
        papszOptions, "GRID_CELL_ENCODING", "grid-value-is-center"));
5785
0
    m_bGridCellEncodingAsCO =
5786
0
        CSLFetchNameValue(papszOptions, "GRID_CELL_ENCODING") != nullptr;
5787
0
    CPLString osUom(CSLFetchNameValueDef(papszOptions, "UOM", ""));
5788
0
    CPLString osFieldName(
5789
0
        CSLFetchNameValueDef(papszOptions, "FIELD_NAME", "Height"));
5790
0
    CPLString osQuantityDefinition(
5791
0
        CSLFetchNameValueDef(papszOptions, "QUANTITY_DEFINITION", "Height"));
5792
5793
0
    pszSQL = sqlite3_mprintf(
5794
0
        "INSERT INTO gpkg_2d_gridded_coverage_ancillary "
5795
0
        "(tile_matrix_set_name, datatype, scale, offset, precision, "
5796
0
        "grid_cell_encoding, uom, field_name, quantity_definition) "
5797
0
        "VALUES (%Q, '%s', %.17g, %.17g, %.17g, %Q, %Q, %Q, %Q)",
5798
0
        m_osRasterTable.c_str(),
5799
0
        (m_eTF == GPKG_TF_PNG_16BIT) ? "integer" : "float", m_dfScale,
5800
0
        m_dfOffset, m_dfPrecision, osGridCellEncoding.c_str(),
5801
0
        osUom.empty() ? nullptr : osUom.c_str(), osFieldName.c_str(),
5802
0
        osQuantityDefinition.c_str());
5803
0
    m_osSQLInsertIntoGpkg2dGriddedCoverageAncillary = pszSQL;
5804
0
    sqlite3_free(pszSQL);
5805
5806
    // Requirement 3 /gpkg-spatial-ref-sys-row
5807
0
    auto oResultTable = SQLQuery(
5808
0
        hDB, "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_id = 4979 LIMIT 2");
5809
0
    bool bHasEPSG4979 = (oResultTable && oResultTable->RowCount() == 1);
5810
0
    if (!bHasEPSG4979)
5811
0
    {
5812
0
        if (!m_bHasDefinition12_063 &&
5813
0
            !ConvertGpkgSpatialRefSysToExtensionWkt2(/*bForceEpoch=*/false))
5814
0
        {
5815
0
            return false;
5816
0
        }
5817
5818
        // This is WKT 2...
5819
0
        const char *pszWKT =
5820
0
            "GEODCRS[\"WGS 84\","
5821
0
            "DATUM[\"World Geodetic System 1984\","
5822
0
            "  ELLIPSOID[\"WGS 84\",6378137,298.257223563,"
5823
0
            "LENGTHUNIT[\"metre\",1.0]]],"
5824
0
            "CS[ellipsoidal,3],"
5825
0
            "  AXIS[\"latitude\",north,ORDER[1],ANGLEUNIT[\"degree\","
5826
0
            "0.0174532925199433]],"
5827
0
            "  AXIS[\"longitude\",east,ORDER[2],ANGLEUNIT[\"degree\","
5828
0
            "0.0174532925199433]],"
5829
0
            "  AXIS[\"ellipsoidal height\",up,ORDER[3],"
5830
0
            "LENGTHUNIT[\"metre\",1.0]],"
5831
0
            "ID[\"EPSG\",4979]]";
5832
5833
0
        pszSQL = sqlite3_mprintf(
5834
0
            "INSERT INTO gpkg_spatial_ref_sys "
5835
0
            "(srs_name,srs_id,organization,organization_coordsys_id,"
5836
0
            "definition,definition_12_063) VALUES "
5837
0
            "('WGS 84 3D', 4979, 'EPSG', 4979, 'undefined', '%q')",
5838
0
            pszWKT);
5839
0
        osSQL += ";";
5840
0
        osSQL += pszSQL;
5841
0
        sqlite3_free(pszSQL);
5842
0
    }
5843
5844
0
    return SQLCommand(hDB, osSQL) == OGRERR_NONE;
5845
0
}
5846
5847
/************************************************************************/
5848
/*                    HasGriddedCoverageAncillaryTable()                */
5849
/************************************************************************/
5850
5851
bool GDALGeoPackageDataset::HasGriddedCoverageAncillaryTable()
5852
0
{
5853
0
    auto oResultTable = SQLQuery(
5854
0
        hDB, "SELECT * FROM sqlite_master WHERE type IN ('table', 'view') AND "
5855
0
             "name = 'gpkg_2d_gridded_coverage_ancillary'");
5856
0
    bool bHasTable = (oResultTable && oResultTable->RowCount() == 1);
5857
0
    return bHasTable;
5858
0
}
5859
5860
/************************************************************************/
5861
/*                      GetUnderlyingDataset()                          */
5862
/************************************************************************/
5863
5864
static GDALDataset *GetUnderlyingDataset(GDALDataset *poSrcDS)
5865
0
{
5866
0
    if (auto poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
5867
0
    {
5868
0
        auto poTmpDS = poVRTDS->GetSingleSimpleSource();
5869
0
        if (poTmpDS)
5870
0
            return poTmpDS;
5871
0
    }
5872
5873
0
    return poSrcDS;
5874
0
}
5875
5876
/************************************************************************/
5877
/*                            CreateCopy()                              */
5878
/************************************************************************/
5879
5880
typedef struct
5881
{
5882
    const char *pszName;
5883
    GDALResampleAlg eResampleAlg;
5884
} WarpResamplingAlg;
5885
5886
static const WarpResamplingAlg asResamplingAlg[] = {
5887
    {"NEAREST", GRA_NearestNeighbour},
5888
    {"BILINEAR", GRA_Bilinear},
5889
    {"CUBIC", GRA_Cubic},
5890
    {"CUBICSPLINE", GRA_CubicSpline},
5891
    {"LANCZOS", GRA_Lanczos},
5892
    {"MODE", GRA_Mode},
5893
    {"AVERAGE", GRA_Average},
5894
    {"RMS", GRA_RMS},
5895
};
5896
5897
GDALDataset *GDALGeoPackageDataset::CreateCopy(const char *pszFilename,
5898
                                               GDALDataset *poSrcDS,
5899
                                               int bStrict, char **papszOptions,
5900
                                               GDALProgressFunc pfnProgress,
5901
                                               void *pProgressData)
5902
0
{
5903
0
    const int nBands = poSrcDS->GetRasterCount();
5904
0
    if (nBands == 0)
5905
0
    {
5906
0
        GDALDataset *poDS = nullptr;
5907
0
        GDALDriver *poThisDriver =
5908
0
            GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5909
0
        if (poThisDriver != nullptr)
5910
0
        {
5911
0
            poDS = poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS,
5912
0
                                                   bStrict, papszOptions,
5913
0
                                                   pfnProgress, pProgressData);
5914
0
        }
5915
0
        return poDS;
5916
0
    }
5917
5918
0
    const char *pszTilingScheme =
5919
0
        CSLFetchNameValueDef(papszOptions, "TILING_SCHEME", "CUSTOM");
5920
5921
0
    CPLStringList apszUpdatedOptions(CSLDuplicate(papszOptions));
5922
0
    if (CPLTestBool(
5923
0
            CSLFetchNameValueDef(papszOptions, "APPEND_SUBDATASET", "NO")) &&
5924
0
        CSLFetchNameValue(papszOptions, "RASTER_TABLE") == nullptr)
5925
0
    {
5926
0
        const std::string osBasename(CPLGetBasenameSafe(
5927
0
            GetUnderlyingDataset(poSrcDS)->GetDescription()));
5928
0
        apszUpdatedOptions.SetNameValue("RASTER_TABLE", osBasename.c_str());
5929
0
    }
5930
5931
0
    if (nBands != 1 && nBands != 2 && nBands != 3 && nBands != 4)
5932
0
    {
5933
0
        CPLError(CE_Failure, CPLE_NotSupported,
5934
0
                 "Only 1 (Grey/ColorTable), 2 (Grey+Alpha), 3 (RGB) or "
5935
0
                 "4 (RGBA) band dataset supported");
5936
0
        return nullptr;
5937
0
    }
5938
5939
0
    const char *pszUnitType = poSrcDS->GetRasterBand(1)->GetUnitType();
5940
0
    if (CSLFetchNameValue(papszOptions, "UOM") == nullptr && pszUnitType &&
5941
0
        !EQUAL(pszUnitType, ""))
5942
0
    {
5943
0
        apszUpdatedOptions.SetNameValue("UOM", pszUnitType);
5944
0
    }
5945
5946
0
    if (EQUAL(pszTilingScheme, "CUSTOM"))
5947
0
    {
5948
0
        if (CSLFetchNameValue(papszOptions, "ZOOM_LEVEL"))
5949
0
        {
5950
0
            CPLError(CE_Failure, CPLE_NotSupported,
5951
0
                     "ZOOM_LEVEL only supported for TILING_SCHEME != CUSTOM");
5952
0
            return nullptr;
5953
0
        }
5954
5955
0
        GDALGeoPackageDataset *poDS = nullptr;
5956
0
        GDALDriver *poThisDriver =
5957
0
            GDALDriver::FromHandle(GDALGetDriverByName("GPKG"));
5958
0
        if (poThisDriver != nullptr)
5959
0
        {
5960
0
            apszUpdatedOptions.SetNameValue("SKIP_HOLES", "YES");
5961
0
            poDS = cpl::down_cast<GDALGeoPackageDataset *>(
5962
0
                poThisDriver->DefaultCreateCopy(pszFilename, poSrcDS, bStrict,
5963
0
                                                apszUpdatedOptions, pfnProgress,
5964
0
                                                pProgressData));
5965
5966
0
            if (poDS != nullptr &&
5967
0
                poSrcDS->GetRasterBand(1)->GetRasterDataType() == GDT_UInt8 &&
5968
0
                nBands <= 3)
5969
0
            {
5970
0
                poDS->m_nBandCountFromMetadata = nBands;
5971
0
                poDS->m_bMetadataDirty = true;
5972
0
            }
5973
0
        }
5974
0
        if (poDS)
5975
0
            poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
5976
0
        return poDS;
5977
0
    }
5978
5979
0
    const auto poTS = GetTilingScheme(pszTilingScheme);
5980
0
    if (!poTS)
5981
0
    {
5982
0
        return nullptr;
5983
0
    }
5984
0
    const int nEPSGCode = poTS->nEPSGCode;
5985
5986
0
    OGRSpatialReference oSRS;
5987
0
    if (oSRS.importFromEPSG(nEPSGCode) != OGRERR_NONE)
5988
0
    {
5989
0
        return nullptr;
5990
0
    }
5991
0
    char *pszWKT = nullptr;
5992
0
    oSRS.exportToWkt(&pszWKT);
5993
0
    char **papszTO = CSLSetNameValue(nullptr, "DST_SRS", pszWKT);
5994
5995
0
    void *hTransformArg = nullptr;
5996
5997
    // Hack to compensate for GDALSuggestedWarpOutput2() failure (or not
5998
    // ideal suggestion with PROJ 8) when reprojecting latitude = +/- 90 to
5999
    // EPSG:3857.
6000
0
    GDALGeoTransform srcGT;
6001
0
    std::unique_ptr<GDALDataset> poTmpDS;
6002
0
    bool bEPSG3857Adjust = false;
6003
0
    if (nEPSGCode == 3857 && poSrcDS->GetGeoTransform(srcGT) == CE_None &&
6004
0
        srcGT[2] == 0 && srcGT[4] == 0 && srcGT[5] < 0)
6005
0
    {
6006
0
        const auto poSrcSRS = poSrcDS->GetSpatialRef();
6007
0
        if (poSrcSRS && poSrcSRS->IsGeographic())
6008
0
        {
6009
0
            double maxLat = srcGT[3];
6010
0
            double minLat = srcGT[3] + poSrcDS->GetRasterYSize() * srcGT[5];
6011
            // Corresponds to the latitude of below MAX_GM
6012
0
            constexpr double MAX_LAT = 85.0511287798066;
6013
0
            bool bModified = false;
6014
0
            if (maxLat > MAX_LAT)
6015
0
            {
6016
0
                maxLat = MAX_LAT;
6017
0
                bModified = true;
6018
0
            }
6019
0
            if (minLat < -MAX_LAT)
6020
0
            {
6021
0
                minLat = -MAX_LAT;
6022
0
                bModified = true;
6023
0
            }
6024
0
            if (bModified)
6025
0
            {
6026
0
                CPLStringList aosOptions;
6027
0
                aosOptions.AddString("-of");
6028
0
                aosOptions.AddString("VRT");
6029
0
                aosOptions.AddString("-projwin");
6030
0
                aosOptions.AddString(CPLSPrintf("%.17g", srcGT[0]));
6031
0
                aosOptions.AddString(CPLSPrintf("%.17g", maxLat));
6032
0
                aosOptions.AddString(CPLSPrintf(
6033
0
                    "%.17g", srcGT[0] + poSrcDS->GetRasterXSize() * srcGT[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
    GDALGeoTransform gt;
6066
0
    double adfExtent[4];
6067
0
    int nXSize, nYSize;
6068
6069
0
    if (GDALSuggestedWarpOutput2(poSrcDS, psInfo->pfnTransform, hTransformArg,
6070
0
                                 gt.data(), &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 = gt[3];
6089
0
        double minNorthing = gt[3] + gt[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
            gt[3] = maxNorthing;
6104
0
            nYSize = int((maxNorthing - minNorthing) / (-gt[5]) + 0.5);
6105
0
            adfExtent[1] = maxNorthing + nYSize * gt[5];
6106
0
            adfExtent[3] = maxNorthing;
6107
0
        }
6108
0
    }
6109
6110
0
    double dfComputedRes = gt[1];
6111
0
    double dfPrevRes = 0.0;
6112
0
    double dfRes = 0.0;
6113
0
    int nZoomLevel = 0;  // Used after for.
6114
0
    const char *pszZoomLevel = CSLFetchNameValue(papszOptions, "ZOOM_LEVEL");
6115
0
    if (pszZoomLevel)
6116
0
    {
6117
0
        nZoomLevel = atoi(pszZoomLevel);
6118
6119
0
        int nMaxZoomLevelForThisTM = MAX_ZOOM_LEVEL;
6120
0
        while ((1 << nMaxZoomLevelForThisTM) >
6121
0
                   INT_MAX / poTS->nTileXCountZoomLevel0 ||
6122
0
               (1 << nMaxZoomLevelForThisTM) >
6123
0
                   INT_MAX / poTS->nTileYCountZoomLevel0)
6124
0
        {
6125
0
            --nMaxZoomLevelForThisTM;
6126
0
        }
6127
6128
0
        if (nZoomLevel < 0 || nZoomLevel > nMaxZoomLevelForThisTM)
6129
0
        {
6130
0
            CPLError(CE_Failure, CPLE_AppDefined,
6131
0
                     "ZOOM_LEVEL = %s is invalid. It should be in [0,%d] range",
6132
0
                     pszZoomLevel, nMaxZoomLevelForThisTM);
6133
0
            CPLFree(pszWKT);
6134
0
            CSLDestroy(papszTO);
6135
0
            return nullptr;
6136
0
        }
6137
0
    }
6138
0
    else
6139
0
    {
6140
0
        for (; nZoomLevel < MAX_ZOOM_LEVEL; nZoomLevel++)
6141
0
        {
6142
0
            dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6143
0
            if (dfComputedRes > dfRes ||
6144
0
                fabs(dfComputedRes - dfRes) / dfRes <= 1e-8)
6145
0
                break;
6146
0
            dfPrevRes = dfRes;
6147
0
        }
6148
0
        if (nZoomLevel == MAX_ZOOM_LEVEL ||
6149
0
            (1 << nZoomLevel) > INT_MAX / poTS->nTileXCountZoomLevel0 ||
6150
0
            (1 << nZoomLevel) > INT_MAX / poTS->nTileYCountZoomLevel0)
6151
0
        {
6152
0
            CPLError(CE_Failure, CPLE_AppDefined,
6153
0
                     "Could not find an appropriate zoom level");
6154
0
            CPLFree(pszWKT);
6155
0
            CSLDestroy(papszTO);
6156
0
            return nullptr;
6157
0
        }
6158
6159
0
        if (nZoomLevel > 0 && fabs(dfComputedRes - dfRes) / dfRes > 1e-8)
6160
0
        {
6161
0
            const char *pszZoomLevelStrategy = CSLFetchNameValueDef(
6162
0
                papszOptions, "ZOOM_LEVEL_STRATEGY", "AUTO");
6163
0
            if (EQUAL(pszZoomLevelStrategy, "LOWER"))
6164
0
            {
6165
0
                nZoomLevel--;
6166
0
            }
6167
0
            else if (EQUAL(pszZoomLevelStrategy, "UPPER"))
6168
0
            {
6169
                /* do nothing */
6170
0
            }
6171
0
            else
6172
0
            {
6173
0
                if (dfPrevRes / dfComputedRes < dfComputedRes / dfRes)
6174
0
                    nZoomLevel--;
6175
0
            }
6176
0
        }
6177
0
    }
6178
6179
0
    dfRes = poTS->dfPixelXSizeZoomLevel0 / (1 << nZoomLevel);
6180
6181
0
    double dfMinX = adfExtent[0];
6182
0
    double dfMinY = adfExtent[1];
6183
0
    double dfMaxX = adfExtent[2];
6184
0
    double dfMaxY = adfExtent[3];
6185
6186
0
    nXSize = static_cast<int>(0.5 + (dfMaxX - dfMinX) / dfRes);
6187
0
    nYSize = static_cast<int>(0.5 + (dfMaxY - dfMinY) / dfRes);
6188
0
    gt[1] = dfRes;
6189
0
    gt[5] = -dfRes;
6190
6191
0
    const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
6192
0
    int nTargetBands = nBands;
6193
    /* For grey level or RGB, if there's reprojection involved, add an alpha */
6194
    /* channel */
6195
0
    if (eDT == GDT_UInt8 &&
6196
0
        ((nBands == 1 &&
6197
0
          poSrcDS->GetRasterBand(1)->GetColorTable() == nullptr) ||
6198
0
         nBands == 3))
6199
0
    {
6200
0
        OGRSpatialReference oSrcSRS;
6201
0
        oSrcSRS.SetFromUserInput(poSrcDS->GetProjectionRef());
6202
0
        oSrcSRS.AutoIdentifyEPSG();
6203
0
        if (oSrcSRS.GetAuthorityCode(nullptr) == nullptr ||
6204
0
            atoi(oSrcSRS.GetAuthorityCode(nullptr)) != nEPSGCode)
6205
0
        {
6206
0
            nTargetBands++;
6207
0
        }
6208
0
    }
6209
6210
0
    GDALResampleAlg eResampleAlg = GRA_Bilinear;
6211
0
    const char *pszResampling = CSLFetchNameValue(papszOptions, "RESAMPLING");
6212
0
    if (pszResampling)
6213
0
    {
6214
0
        for (size_t iAlg = 0;
6215
0
             iAlg < sizeof(asResamplingAlg) / sizeof(asResamplingAlg[0]);
6216
0
             iAlg++)
6217
0
        {
6218
0
            if (EQUAL(pszResampling, asResamplingAlg[iAlg].pszName))
6219
0
            {
6220
0
                eResampleAlg = asResamplingAlg[iAlg].eResampleAlg;
6221
0
                break;
6222
0
            }
6223
0
        }
6224
0
    }
6225
6226
0
    if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
6227
0
        eResampleAlg != GRA_NearestNeighbour && eResampleAlg != GRA_Mode)
6228
0
    {
6229
0
        CPLError(
6230
0
            CE_Warning, CPLE_AppDefined,
6231
0
            "Input dataset has a color table, which will likely lead to "
6232
0
            "bad results when using a resampling method other than "
6233
0
            "nearest neighbour or mode. Converting the dataset to 24/32 bit "
6234
0
            "(e.g. with gdal_translate -expand rgb/rgba) is advised.");
6235
0
    }
6236
6237
0
    auto poDS = std::make_unique<GDALGeoPackageDataset>();
6238
0
    if (!(poDS->Create(pszFilename, nXSize, nYSize, nTargetBands, eDT,
6239
0
                       apszUpdatedOptions)))
6240
0
    {
6241
0
        CPLFree(pszWKT);
6242
0
        CSLDestroy(papszTO);
6243
0
        return nullptr;
6244
0
    }
6245
6246
    // Assign nodata values before the SetGeoTransform call.
6247
    // SetGeoTransform will trigger creation of the overview datasets for each
6248
    // zoom level and at that point the nodata value needs to be known.
6249
0
    int bHasNoData = FALSE;
6250
0
    double dfNoDataValue =
6251
0
        poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoData);
6252
0
    if (eDT != GDT_UInt8 && bHasNoData)
6253
0
    {
6254
0
        poDS->GetRasterBand(1)->SetNoDataValue(dfNoDataValue);
6255
0
    }
6256
6257
0
    poDS->SetGeoTransform(gt);
6258
0
    poDS->SetProjection(pszWKT);
6259
0
    CPLFree(pszWKT);
6260
0
    pszWKT = nullptr;
6261
0
    if (nTargetBands == 1 && nBands == 1 &&
6262
0
        poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr)
6263
0
    {
6264
0
        poDS->GetRasterBand(1)->SetColorTable(
6265
0
            poSrcDS->GetRasterBand(1)->GetColorTable());
6266
0
    }
6267
6268
0
    hTransformArg =
6269
0
        GDALCreateGenImgProjTransformer2(poSrcDS, poDS.get(), papszTO);
6270
0
    CSLDestroy(papszTO);
6271
0
    if (hTransformArg == nullptr)
6272
0
    {
6273
0
        return nullptr;
6274
0
    }
6275
6276
0
    poDS->SetMetadata(poSrcDS->GetMetadata());
6277
6278
    /* -------------------------------------------------------------------- */
6279
    /*      Warp the transformer with a linear approximator                 */
6280
    /* -------------------------------------------------------------------- */
6281
0
    hTransformArg = GDALCreateApproxTransformer(GDALGenImgProjTransform,
6282
0
                                                hTransformArg, 0.125);
6283
0
    GDALApproxTransformerOwnsSubtransformer(hTransformArg, TRUE);
6284
6285
    /* -------------------------------------------------------------------- */
6286
    /*      Setup warp options.                                             */
6287
    /* -------------------------------------------------------------------- */
6288
0
    GDALWarpOptions *psWO = GDALCreateWarpOptions();
6289
6290
0
    psWO->papszWarpOptions = CSLSetNameValue(nullptr, "OPTIMIZE_SIZE", "YES");
6291
0
    psWO->papszWarpOptions =
6292
0
        CSLSetNameValue(psWO->papszWarpOptions, "SAMPLE_GRID", "YES");
6293
0
    if (bHasNoData)
6294
0
    {
6295
0
        if (dfNoDataValue == 0.0)
6296
0
        {
6297
            // Do not initialize in the case where nodata != 0, since we
6298
            // want the GeoPackage driver to return empty tiles at the nodata
6299
            // value instead of 0 as GDAL core would
6300
0
            psWO->papszWarpOptions =
6301
0
                CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "0");
6302
0
        }
6303
6304
0
        psWO->padfSrcNoDataReal =
6305
0
            static_cast<double *>(CPLMalloc(sizeof(double)));
6306
0
        psWO->padfSrcNoDataReal[0] = dfNoDataValue;
6307
6308
0
        psWO->padfDstNoDataReal =
6309
0
            static_cast<double *>(CPLMalloc(sizeof(double)));
6310
0
        psWO->padfDstNoDataReal[0] = dfNoDataValue;
6311
0
    }
6312
0
    psWO->eWorkingDataType = eDT;
6313
0
    psWO->eResampleAlg = eResampleAlg;
6314
6315
0
    psWO->hSrcDS = poSrcDS;
6316
0
    psWO->hDstDS = poDS.get();
6317
6318
0
    psWO->pfnTransformer = GDALApproxTransform;
6319
0
    psWO->pTransformerArg = hTransformArg;
6320
6321
0
    psWO->pfnProgress = pfnProgress;
6322
0
    psWO->pProgressArg = pProgressData;
6323
6324
    /* -------------------------------------------------------------------- */
6325
    /*      Setup band mapping.                                             */
6326
    /* -------------------------------------------------------------------- */
6327
6328
0
    if (nBands == 2 || nBands == 4)
6329
0
        psWO->nBandCount = nBands - 1;
6330
0
    else
6331
0
        psWO->nBandCount = nBands;
6332
6333
0
    psWO->panSrcBands =
6334
0
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6335
0
    psWO->panDstBands =
6336
0
        static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
6337
6338
0
    for (int i = 0; i < psWO->nBandCount; i++)
6339
0
    {
6340
0
        psWO->panSrcBands[i] = i + 1;
6341
0
        psWO->panDstBands[i] = i + 1;
6342
0
    }
6343
6344
0
    if (nBands == 2 || nBands == 4)
6345
0
    {
6346
0
        psWO->nSrcAlphaBand = nBands;
6347
0
    }
6348
0
    if (nTargetBands == 2 || nTargetBands == 4)
6349
0
    {
6350
0
        psWO->nDstAlphaBand = nTargetBands;
6351
0
    }
6352
6353
    /* -------------------------------------------------------------------- */
6354
    /*      Initialize and execute the warp.                                */
6355
    /* -------------------------------------------------------------------- */
6356
0
    GDALWarpOperation oWO;
6357
6358
0
    CPLErr eErr = oWO.Initialize(psWO);
6359
0
    if (eErr == CE_None)
6360
0
    {
6361
        /*if( bMulti )
6362
            eErr = oWO.ChunkAndWarpMulti( 0, 0, nXSize, nYSize );
6363
        else*/
6364
0
        eErr = oWO.ChunkAndWarpImage(0, 0, nXSize, nYSize);
6365
0
    }
6366
0
    if (eErr != CE_None)
6367
0
    {
6368
0
        poDS.reset();
6369
0
    }
6370
6371
0
    GDALDestroyTransformer(hTransformArg);
6372
0
    GDALDestroyWarpOptions(psWO);
6373
6374
0
    if (poDS)
6375
0
        poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
6376
6377
0
    return poDS.release();
6378
0
}
6379
6380
/************************************************************************/
6381
/*                        ParseCompressionOptions()                     */
6382
/************************************************************************/
6383
6384
void GDALGeoPackageDataset::ParseCompressionOptions(char **papszOptions)
6385
20
{
6386
20
    const char *pszZLevel = CSLFetchNameValue(papszOptions, "ZLEVEL");
6387
20
    if (pszZLevel)
6388
0
        m_nZLevel = atoi(pszZLevel);
6389
6390
20
    const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY");
6391
20
    if (pszQuality)
6392
0
        m_nQuality = atoi(pszQuality);
6393
6394
20
    const char *pszDither = CSLFetchNameValue(papszOptions, "DITHER");
6395
20
    if (pszDither)
6396
0
        m_bDither = CPLTestBool(pszDither);
6397
20
}
6398
6399
/************************************************************************/
6400
/*                          RegisterWebPExtension()                     */
6401
/************************************************************************/
6402
6403
bool GDALGeoPackageDataset::RegisterWebPExtension()
6404
0
{
6405
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6406
0
        return false;
6407
6408
0
    char *pszSQL = sqlite3_mprintf(
6409
0
        "INSERT INTO gpkg_extensions "
6410
0
        "(table_name, column_name, extension_name, definition, scope) "
6411
0
        "VALUES "
6412
0
        "('%q', 'tile_data', 'gpkg_webp', "
6413
0
        "'http://www.geopackage.org/spec120/#extension_tiles_webp', "
6414
0
        "'read-write')",
6415
0
        m_osRasterTable.c_str());
6416
0
    const OGRErr eErr = SQLCommand(hDB, pszSQL);
6417
0
    sqlite3_free(pszSQL);
6418
6419
0
    return OGRERR_NONE == eErr;
6420
0
}
6421
6422
/************************************************************************/
6423
/*                       RegisterZoomOtherExtension()                   */
6424
/************************************************************************/
6425
6426
bool GDALGeoPackageDataset::RegisterZoomOtherExtension()
6427
0
{
6428
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
6429
0
        return false;
6430
6431
0
    char *pszSQL = sqlite3_mprintf(
6432
0
        "INSERT INTO gpkg_extensions "
6433
0
        "(table_name, column_name, extension_name, definition, scope) "
6434
0
        "VALUES "
6435
0
        "('%q', 'tile_data', 'gpkg_zoom_other', "
6436
0
        "'http://www.geopackage.org/spec120/#extension_zoom_other_intervals', "
6437
0
        "'read-write')",
6438
0
        m_osRasterTable.c_str());
6439
0
    const OGRErr eErr = SQLCommand(hDB, pszSQL);
6440
0
    sqlite3_free(pszSQL);
6441
0
    return OGRERR_NONE == eErr;
6442
0
}
6443
6444
/************************************************************************/
6445
/*                              GetLayer()                              */
6446
/************************************************************************/
6447
6448
const OGRLayer *GDALGeoPackageDataset::GetLayer(int iLayer) const
6449
6450
1.48M
{
6451
1.48M
    if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
6452
0
        return nullptr;
6453
1.48M
    else
6454
1.48M
        return m_apoLayers[iLayer].get();
6455
1.48M
}
6456
6457
/************************************************************************/
6458
/*                           LaunderName()                              */
6459
/************************************************************************/
6460
6461
/** Launder identifiers (table, column names) according to guidance at
6462
 * https://www.geopackage.org/guidance/getting-started.html:
6463
 * "For maximum interoperability, start your database identifiers (table names,
6464
 * column names, etc.) with a lowercase character and only use lowercase
6465
 * characters, numbers 0-9, and underscores (_)."
6466
 */
6467
6468
/* static */
6469
std::string GDALGeoPackageDataset::LaunderName(const std::string &osStr)
6470
0
{
6471
0
    char *pszASCII = CPLUTF8ForceToASCII(osStr.c_str(), '_');
6472
0
    const std::string osStrASCII(pszASCII);
6473
0
    CPLFree(pszASCII);
6474
6475
0
    std::string osRet;
6476
0
    osRet.reserve(osStrASCII.size());
6477
6478
0
    for (size_t i = 0; i < osStrASCII.size(); ++i)
6479
0
    {
6480
0
        if (osRet.empty())
6481
0
        {
6482
0
            if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6483
0
            {
6484
0
                osRet += (osStrASCII[i] - 'A' + 'a');
6485
0
            }
6486
0
            else if (osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z')
6487
0
            {
6488
0
                osRet += osStrASCII[i];
6489
0
            }
6490
0
            else
6491
0
            {
6492
0
                continue;
6493
0
            }
6494
0
        }
6495
0
        else if (osStrASCII[i] >= 'A' && osStrASCII[i] <= 'Z')
6496
0
        {
6497
0
            osRet += (osStrASCII[i] - 'A' + 'a');
6498
0
        }
6499
0
        else if ((osStrASCII[i] >= 'a' && osStrASCII[i] <= 'z') ||
6500
0
                 (osStrASCII[i] >= '0' && osStrASCII[i] <= '9') ||
6501
0
                 osStrASCII[i] == '_')
6502
0
        {
6503
0
            osRet += osStrASCII[i];
6504
0
        }
6505
0
        else
6506
0
        {
6507
0
            osRet += '_';
6508
0
        }
6509
0
    }
6510
6511
0
    if (osRet.empty() && !osStrASCII.empty())
6512
0
        return LaunderName(std::string("x").append(osStrASCII));
6513
6514
0
    if (osRet != osStr)
6515
0
    {
6516
0
        CPLDebug("PG", "LaunderName('%s') -> '%s'", osStr.c_str(),
6517
0
                 osRet.c_str());
6518
0
    }
6519
6520
0
    return osRet;
6521
0
}
6522
6523
/************************************************************************/
6524
/*                          ICreateLayer()                              */
6525
/************************************************************************/
6526
6527
OGRLayer *
6528
GDALGeoPackageDataset::ICreateLayer(const char *pszLayerName,
6529
                                    const OGRGeomFieldDefn *poSrcGeomFieldDefn,
6530
                                    CSLConstList papszOptions)
6531
3.76k
{
6532
    /* -------------------------------------------------------------------- */
6533
    /*      Verify we are in update mode.                                   */
6534
    /* -------------------------------------------------------------------- */
6535
3.76k
    if (!GetUpdate())
6536
0
    {
6537
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
6538
0
                 "Data source %s opened read-only.\n"
6539
0
                 "New layer %s cannot be created.\n",
6540
0
                 m_pszFilename, pszLayerName);
6541
6542
0
        return nullptr;
6543
0
    }
6544
6545
3.76k
    const bool bLaunder =
6546
3.76k
        CPLTestBool(CSLFetchNameValueDef(papszOptions, "LAUNDER", "NO"));
6547
3.76k
    const std::string osTableName(bLaunder ? LaunderName(pszLayerName)
6548
3.76k
                                           : std::string(pszLayerName));
6549
6550
3.76k
    const auto eGType =
6551
3.76k
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
6552
3.76k
    const auto poSpatialRef =
6553
3.76k
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
6554
6555
3.76k
    if (!m_bHasGPKGGeometryColumns)
6556
0
    {
6557
0
        if (SQLCommand(hDB, pszCREATE_GPKG_GEOMETRY_COLUMNS) != OGRERR_NONE)
6558
0
        {
6559
0
            return nullptr;
6560
0
        }
6561
0
        m_bHasGPKGGeometryColumns = true;
6562
0
    }
6563
6564
    // Check identifier unicity
6565
3.76k
    const char *pszIdentifier = CSLFetchNameValue(papszOptions, "IDENTIFIER");
6566
3.76k
    if (pszIdentifier != nullptr && pszIdentifier[0] == '\0')
6567
0
        pszIdentifier = nullptr;
6568
3.76k
    if (pszIdentifier != nullptr)
6569
0
    {
6570
0
        for (auto &poLayer : m_apoLayers)
6571
0
        {
6572
0
            const char *pszOtherIdentifier =
6573
0
                poLayer->GetMetadataItem("IDENTIFIER");
6574
0
            if (pszOtherIdentifier == nullptr)
6575
0
                pszOtherIdentifier = poLayer->GetName();
6576
0
            if (pszOtherIdentifier != nullptr &&
6577
0
                EQUAL(pszOtherIdentifier, pszIdentifier) &&
6578
0
                !EQUAL(poLayer->GetName(), osTableName.c_str()))
6579
0
            {
6580
0
                CPLError(CE_Failure, CPLE_AppDefined,
6581
0
                         "Identifier %s is already used by table %s",
6582
0
                         pszIdentifier, poLayer->GetName());
6583
0
                return nullptr;
6584
0
            }
6585
0
        }
6586
6587
        // In case there would be table in gpkg_contents not listed as a
6588
        // vector layer
6589
0
        char *pszSQL = sqlite3_mprintf(
6590
0
            "SELECT table_name FROM gpkg_contents WHERE identifier = '%q' "
6591
0
            "LIMIT 2",
6592
0
            pszIdentifier);
6593
0
        auto oResult = SQLQuery(hDB, pszSQL);
6594
0
        sqlite3_free(pszSQL);
6595
0
        if (oResult && oResult->RowCount() > 0 &&
6596
0
            oResult->GetValue(0, 0) != nullptr &&
6597
0
            !EQUAL(oResult->GetValue(0, 0), osTableName.c_str()))
6598
0
        {
6599
0
            CPLError(CE_Failure, CPLE_AppDefined,
6600
0
                     "Identifier %s is already used by table %s", pszIdentifier,
6601
0
                     oResult->GetValue(0, 0));
6602
0
            return nullptr;
6603
0
        }
6604
0
    }
6605
6606
    /* Read GEOMETRY_NAME option */
6607
3.76k
    const char *pszGeomColumnName =
6608
3.76k
        CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
6609
3.76k
    if (pszGeomColumnName == nullptr) /* deprecated name */
6610
3.30k
        pszGeomColumnName = CSLFetchNameValue(papszOptions, "GEOMETRY_COLUMN");
6611
3.76k
    if (pszGeomColumnName == nullptr && poSrcGeomFieldDefn)
6612
748
    {
6613
748
        pszGeomColumnName = poSrcGeomFieldDefn->GetNameRef();
6614
748
        if (pszGeomColumnName && pszGeomColumnName[0] == 0)
6615
748
            pszGeomColumnName = nullptr;
6616
748
    }
6617
3.76k
    if (pszGeomColumnName == nullptr)
6618
3.30k
        pszGeomColumnName = "geom";
6619
3.76k
    const bool bGeomNullable =
6620
3.76k
        CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true);
6621
6622
    /* Read FID option */
6623
3.76k
    const char *pszFIDColumnName = CSLFetchNameValue(papszOptions, "FID");
6624
3.76k
    if (pszFIDColumnName == nullptr)
6625
3.76k
        pszFIDColumnName = "fid";
6626
6627
3.76k
    if (CPLTestBool(CPLGetConfigOption("GPKG_NAME_CHECK", "YES")))
6628
3.76k
    {
6629
3.76k
        if (strspn(pszFIDColumnName, "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") > 0)
6630
0
        {
6631
0
            CPLError(CE_Failure, CPLE_AppDefined,
6632
0
                     "The primary key (%s) name may not contain special "
6633
0
                     "characters or spaces",
6634
0
                     pszFIDColumnName);
6635
0
            return nullptr;
6636
0
        }
6637
6638
        /* Avoiding gpkg prefixes is not an official requirement, but seems wise
6639
         */
6640
3.76k
        if (STARTS_WITH(osTableName.c_str(), "gpkg"))
6641
4
        {
6642
4
            CPLError(CE_Failure, CPLE_AppDefined,
6643
4
                     "The layer name may not begin with 'gpkg' as it is a "
6644
4
                     "reserved geopackage prefix");
6645
4
            return nullptr;
6646
4
        }
6647
6648
        /* Preemptively try and avoid sqlite3 syntax errors due to  */
6649
        /* illegal characters. */
6650
3.75k
        if (strspn(osTableName.c_str(), "`~!@#$%^&*()+-={}|[]\\:\";'<>?,./") >
6651
3.75k
            0)
6652
76
        {
6653
76
            CPLError(
6654
76
                CE_Failure, CPLE_AppDefined,
6655
76
                "The layer name may not contain special characters or spaces");
6656
76
            return nullptr;
6657
76
        }
6658
3.75k
    }
6659
6660
    /* Check for any existing layers that already use this name */
6661
27.6k
    for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6662
23.9k
         iLayer++)
6663
23.9k
    {
6664
23.9k
        if (EQUAL(osTableName.c_str(), m_apoLayers[iLayer]->GetName()))
6665
27
        {
6666
27
            const char *pszOverwrite =
6667
27
                CSLFetchNameValue(papszOptions, "OVERWRITE");
6668
27
            if (pszOverwrite != nullptr && CPLTestBool(pszOverwrite))
6669
0
            {
6670
0
                DeleteLayer(iLayer);
6671
0
            }
6672
27
            else
6673
27
            {
6674
27
                CPLError(CE_Failure, CPLE_AppDefined,
6675
27
                         "Layer %s already exists, CreateLayer failed.\n"
6676
27
                         "Use the layer creation option OVERWRITE=YES to "
6677
27
                         "replace it.",
6678
27
                         osTableName.c_str());
6679
27
                return nullptr;
6680
27
            }
6681
27
        }
6682
23.9k
    }
6683
6684
3.65k
    if (m_apoLayers.size() == 1)
6685
446
    {
6686
        // Async RTree building doesn't play well with multiple layer:
6687
        // SQLite3 locks being hold for a long time, random failed commits,
6688
        // etc.
6689
446
        m_apoLayers[0]->FinishOrDisableThreadedRTree();
6690
446
    }
6691
6692
    /* Create a blank layer. */
6693
3.65k
    auto poLayer =
6694
3.65k
        std::make_unique<OGRGeoPackageTableLayer>(this, osTableName.c_str());
6695
6696
3.65k
    OGRSpatialReference *poSRS = nullptr;
6697
3.65k
    if (poSpatialRef)
6698
438
    {
6699
438
        poSRS = poSpatialRef->Clone();
6700
438
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
6701
438
    }
6702
3.65k
    poLayer->SetCreationParameters(
6703
3.65k
        eGType,
6704
3.65k
        bLaunder ? LaunderName(pszGeomColumnName).c_str() : pszGeomColumnName,
6705
3.65k
        bGeomNullable, poSRS, CSLFetchNameValue(papszOptions, "SRID"),
6706
3.65k
        poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetCoordinatePrecision()
6707
3.65k
                           : OGRGeomCoordinatePrecision(),
6708
3.65k
        CPLTestBool(
6709
3.65k
            CSLFetchNameValueDef(papszOptions, "DISCARD_COORD_LSB", "NO")),
6710
3.65k
        CPLTestBool(CSLFetchNameValueDef(
6711
3.65k
            papszOptions, "UNDO_DISCARD_COORD_LSB_ON_READING", "NO")),
6712
3.65k
        bLaunder ? LaunderName(pszFIDColumnName).c_str() : pszFIDColumnName,
6713
3.65k
        pszIdentifier, CSLFetchNameValue(papszOptions, "DESCRIPTION"));
6714
3.65k
    if (poSRS)
6715
438
    {
6716
438
        poSRS->Release();
6717
438
    }
6718
6719
3.65k
    poLayer->SetLaunder(bLaunder);
6720
6721
    /* Should we create a spatial index ? */
6722
3.65k
    const char *pszSI = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX");
6723
3.65k
    int bCreateSpatialIndex = (pszSI == nullptr || CPLTestBool(pszSI));
6724
3.65k
    if (eGType != wkbNone && bCreateSpatialIndex)
6725
1.17k
    {
6726
1.17k
        poLayer->SetDeferredSpatialIndexCreation(true);
6727
1.17k
    }
6728
6729
3.65k
    poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
6730
3.65k
    poLayer->SetTruncateFieldsFlag(
6731
3.65k
        CPLFetchBool(papszOptions, "TRUNCATE_FIELDS", false));
6732
3.65k
    if (eGType == wkbNone)
6733
2.45k
    {
6734
2.45k
        const char *pszASpatialVariant = CSLFetchNameValueDef(
6735
2.45k
            papszOptions, "ASPATIAL_VARIANT",
6736
2.45k
            m_bNonSpatialTablesNonRegisteredInGpkgContentsFound
6737
2.45k
                ? "NOT_REGISTERED"
6738
2.45k
                : "GPKG_ATTRIBUTES");
6739
2.45k
        GPKGASpatialVariant eASpatialVariant = GPKG_ATTRIBUTES;
6740
2.45k
        if (EQUAL(pszASpatialVariant, "GPKG_ATTRIBUTES"))
6741
2.45k
            eASpatialVariant = GPKG_ATTRIBUTES;
6742
0
        else if (EQUAL(pszASpatialVariant, "OGR_ASPATIAL"))
6743
0
        {
6744
0
            CPLError(CE_Failure, CPLE_NotSupported,
6745
0
                     "ASPATIAL_VARIANT=OGR_ASPATIAL is no longer supported");
6746
0
            return nullptr;
6747
0
        }
6748
0
        else if (EQUAL(pszASpatialVariant, "NOT_REGISTERED"))
6749
0
            eASpatialVariant = NOT_REGISTERED;
6750
0
        else
6751
0
        {
6752
0
            CPLError(CE_Failure, CPLE_NotSupported,
6753
0
                     "Unsupported value for ASPATIAL_VARIANT: %s",
6754
0
                     pszASpatialVariant);
6755
0
            return nullptr;
6756
0
        }
6757
2.45k
        poLayer->SetASpatialVariant(eASpatialVariant);
6758
2.45k
    }
6759
6760
3.65k
    const char *pszDateTimePrecision =
6761
3.65k
        CSLFetchNameValueDef(papszOptions, "DATETIME_PRECISION", "AUTO");
6762
3.65k
    if (EQUAL(pszDateTimePrecision, "MILLISECOND"))
6763
0
    {
6764
0
        poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6765
0
    }
6766
3.65k
    else if (EQUAL(pszDateTimePrecision, "SECOND"))
6767
0
    {
6768
0
        if (m_nUserVersion < GPKG_1_4_VERSION)
6769
0
            CPLError(
6770
0
                CE_Warning, CPLE_AppDefined,
6771
0
                "DATETIME_PRECISION=SECOND is only valid since GeoPackage 1.4");
6772
0
        poLayer->SetDateTimePrecision(OGRISO8601Precision::SECOND);
6773
0
    }
6774
3.65k
    else if (EQUAL(pszDateTimePrecision, "MINUTE"))
6775
0
    {
6776
0
        if (m_nUserVersion < GPKG_1_4_VERSION)
6777
0
            CPLError(
6778
0
                CE_Warning, CPLE_AppDefined,
6779
0
                "DATETIME_PRECISION=MINUTE is only valid since GeoPackage 1.4");
6780
0
        poLayer->SetDateTimePrecision(OGRISO8601Precision::MINUTE);
6781
0
    }
6782
3.65k
    else if (EQUAL(pszDateTimePrecision, "AUTO"))
6783
3.65k
    {
6784
3.65k
        if (m_nUserVersion < GPKG_1_4_VERSION)
6785
0
            poLayer->SetDateTimePrecision(OGRISO8601Precision::MILLISECOND);
6786
3.65k
    }
6787
0
    else
6788
0
    {
6789
0
        CPLError(CE_Failure, CPLE_NotSupported,
6790
0
                 "Unsupported value for DATETIME_PRECISION: %s",
6791
0
                 pszDateTimePrecision);
6792
0
        return nullptr;
6793
0
    }
6794
6795
    // If there was an ogr_empty_table table, we can remove it
6796
    // But do it at dataset closing, otherwise locking performance issues
6797
    // can arise (probably when transactions are used).
6798
3.65k
    m_bRemoveOGREmptyTable = true;
6799
6800
3.65k
    m_apoLayers.emplace_back(std::move(poLayer));
6801
3.65k
    return m_apoLayers.back().get();
6802
3.65k
}
6803
6804
/************************************************************************/
6805
/*                          FindLayerIndex()                            */
6806
/************************************************************************/
6807
6808
int GDALGeoPackageDataset::FindLayerIndex(const char *pszLayerName)
6809
6810
0
{
6811
0
    for (int iLayer = 0; iLayer < static_cast<int>(m_apoLayers.size());
6812
0
         iLayer++)
6813
0
    {
6814
0
        if (EQUAL(pszLayerName, m_apoLayers[iLayer]->GetName()))
6815
0
            return iLayer;
6816
0
    }
6817
0
    return -1;
6818
0
}
6819
6820
/************************************************************************/
6821
/*                       DeleteLayerCommon()                            */
6822
/************************************************************************/
6823
6824
OGRErr GDALGeoPackageDataset::DeleteLayerCommon(const char *pszLayerName)
6825
0
{
6826
    // Temporary remove foreign key checks
6827
0
    const GPKGTemporaryForeignKeyCheckDisabler
6828
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
6829
6830
0
    char *pszSQL = sqlite3_mprintf(
6831
0
        "DELETE FROM gpkg_contents WHERE lower(table_name) = lower('%q')",
6832
0
        pszLayerName);
6833
0
    OGRErr eErr = SQLCommand(hDB, pszSQL);
6834
0
    sqlite3_free(pszSQL);
6835
6836
0
    if (eErr == OGRERR_NONE && HasExtensionsTable())
6837
0
    {
6838
0
        pszSQL = sqlite3_mprintf(
6839
0
            "DELETE FROM gpkg_extensions WHERE lower(table_name) = lower('%q')",
6840
0
            pszLayerName);
6841
0
        eErr = SQLCommand(hDB, pszSQL);
6842
0
        sqlite3_free(pszSQL);
6843
0
    }
6844
6845
0
    if (eErr == OGRERR_NONE && HasMetadataTables())
6846
0
    {
6847
        // Delete from gpkg_metadata metadata records that are only referenced
6848
        // by the table we are about to drop
6849
0
        pszSQL = sqlite3_mprintf(
6850
0
            "DELETE FROM gpkg_metadata WHERE id IN ("
6851
0
            "SELECT DISTINCT md_file_id FROM "
6852
0
            "gpkg_metadata_reference WHERE "
6853
0
            "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6854
0
            "AND id NOT IN ("
6855
0
            "SELECT DISTINCT md_file_id FROM gpkg_metadata_reference WHERE "
6856
0
            "md_file_id IN (SELECT DISTINCT md_file_id FROM "
6857
0
            "gpkg_metadata_reference WHERE "
6858
0
            "lower(table_name) = lower('%q') AND md_parent_id is NULL) "
6859
0
            "AND lower(table_name) <> lower('%q'))",
6860
0
            pszLayerName, pszLayerName, pszLayerName);
6861
0
        eErr = SQLCommand(hDB, pszSQL);
6862
0
        sqlite3_free(pszSQL);
6863
6864
0
        if (eErr == OGRERR_NONE)
6865
0
        {
6866
0
            pszSQL =
6867
0
                sqlite3_mprintf("DELETE FROM gpkg_metadata_reference WHERE "
6868
0
                                "lower(table_name) = lower('%q')",
6869
0
                                pszLayerName);
6870
0
            eErr = SQLCommand(hDB, pszSQL);
6871
0
            sqlite3_free(pszSQL);
6872
0
        }
6873
0
    }
6874
6875
0
    if (eErr == OGRERR_NONE && HasGpkgextRelationsTable())
6876
0
    {
6877
        // Remove reference to potential corresponding mapping table in
6878
        // gpkg_extensions
6879
0
        pszSQL = sqlite3_mprintf(
6880
0
            "DELETE FROM gpkg_extensions WHERE "
6881
0
            "extension_name IN ('related_tables', "
6882
0
            "'gpkg_related_tables') AND lower(table_name) = "
6883
0
            "(SELECT lower(mapping_table_name) FROM gpkgext_relations WHERE "
6884
0
            "lower(base_table_name) = lower('%q') OR "
6885
0
            "lower(related_table_name) = lower('%q') OR "
6886
0
            "lower(mapping_table_name) = lower('%q'))",
6887
0
            pszLayerName, pszLayerName, pszLayerName);
6888
0
        eErr = SQLCommand(hDB, pszSQL);
6889
0
        sqlite3_free(pszSQL);
6890
6891
0
        if (eErr == OGRERR_NONE)
6892
0
        {
6893
            // Remove reference to potential corresponding mapping table in
6894
            // gpkgext_relations
6895
0
            pszSQL =
6896
0
                sqlite3_mprintf("DELETE FROM gpkgext_relations WHERE "
6897
0
                                "lower(base_table_name) = lower('%q') OR "
6898
0
                                "lower(related_table_name) = lower('%q') OR "
6899
0
                                "lower(mapping_table_name) = lower('%q')",
6900
0
                                pszLayerName, pszLayerName, pszLayerName);
6901
0
            eErr = SQLCommand(hDB, pszSQL);
6902
0
            sqlite3_free(pszSQL);
6903
0
        }
6904
6905
0
        if (eErr == OGRERR_NONE && HasExtensionsTable())
6906
0
        {
6907
            // If there is no longer any mapping table, then completely
6908
            // remove any reference to the extension in gpkg_extensions
6909
            // as mandated per the related table specification.
6910
0
            OGRErr err;
6911
0
            if (SQLGetInteger(hDB,
6912
0
                              "SELECT COUNT(*) FROM gpkg_extensions WHERE "
6913
0
                              "extension_name IN ('related_tables', "
6914
0
                              "'gpkg_related_tables') AND "
6915
0
                              "lower(table_name) != 'gpkgext_relations'",
6916
0
                              &err) == 0)
6917
0
            {
6918
0
                eErr = SQLCommand(hDB, "DELETE FROM gpkg_extensions WHERE "
6919
0
                                       "extension_name IN ('related_tables', "
6920
0
                                       "'gpkg_related_tables')");
6921
0
            }
6922
6923
0
            ClearCachedRelationships();
6924
0
        }
6925
0
    }
6926
6927
0
    if (eErr == OGRERR_NONE)
6928
0
    {
6929
0
        pszSQL = sqlite3_mprintf("DROP TABLE \"%w\"", pszLayerName);
6930
0
        eErr = SQLCommand(hDB, pszSQL);
6931
0
        sqlite3_free(pszSQL);
6932
0
    }
6933
6934
    // Check foreign key integrity
6935
0
    if (eErr == OGRERR_NONE)
6936
0
    {
6937
0
        eErr = PragmaCheck("foreign_key_check", "", 0);
6938
0
    }
6939
6940
0
    return eErr;
6941
0
}
6942
6943
/************************************************************************/
6944
/*                            DeleteLayer()                             */
6945
/************************************************************************/
6946
6947
OGRErr GDALGeoPackageDataset::DeleteLayer(int iLayer)
6948
0
{
6949
0
    if (!GetUpdate() || iLayer < 0 ||
6950
0
        iLayer >= static_cast<int>(m_apoLayers.size()))
6951
0
        return OGRERR_FAILURE;
6952
6953
0
    m_apoLayers[iLayer]->ResetReading();
6954
0
    m_apoLayers[iLayer]->SyncToDisk();
6955
6956
0
    CPLString osLayerName = m_apoLayers[iLayer]->GetName();
6957
6958
0
    CPLDebug("GPKG", "DeleteLayer(%s)", osLayerName.c_str());
6959
6960
    // Temporary remove foreign key checks
6961
0
    const GPKGTemporaryForeignKeyCheckDisabler
6962
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
6963
6964
0
    OGRErr eErr = SoftStartTransaction();
6965
6966
0
    if (eErr == OGRERR_NONE)
6967
0
    {
6968
0
        if (m_apoLayers[iLayer]->HasSpatialIndex())
6969
0
            m_apoLayers[iLayer]->DropSpatialIndex();
6970
6971
0
        char *pszSQL =
6972
0
            sqlite3_mprintf("DELETE FROM gpkg_geometry_columns WHERE "
6973
0
                            "lower(table_name) = lower('%q')",
6974
0
                            osLayerName.c_str());
6975
0
        eErr = SQLCommand(hDB, pszSQL);
6976
0
        sqlite3_free(pszSQL);
6977
0
    }
6978
6979
0
    if (eErr == OGRERR_NONE && HasDataColumnsTable())
6980
0
    {
6981
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_data_columns WHERE "
6982
0
                                       "lower(table_name) = lower('%q')",
6983
0
                                       osLayerName.c_str());
6984
0
        eErr = SQLCommand(hDB, pszSQL);
6985
0
        sqlite3_free(pszSQL);
6986
0
    }
6987
6988
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
6989
0
    if (eErr == OGRERR_NONE && m_bHasGPKGOGRContents)
6990
0
    {
6991
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_ogr_contents WHERE "
6992
0
                                       "lower(table_name) = lower('%q')",
6993
0
                                       osLayerName.c_str());
6994
0
        eErr = SQLCommand(hDB, pszSQL);
6995
0
        sqlite3_free(pszSQL);
6996
0
    }
6997
0
#endif
6998
6999
0
    if (eErr == OGRERR_NONE)
7000
0
    {
7001
0
        eErr = DeleteLayerCommon(osLayerName.c_str());
7002
0
    }
7003
7004
0
    if (eErr == OGRERR_NONE)
7005
0
    {
7006
0
        eErr = SoftCommitTransaction();
7007
0
        if (eErr == OGRERR_NONE)
7008
0
        {
7009
            /* Delete the layer object */
7010
0
            m_apoLayers.erase(m_apoLayers.begin() + iLayer);
7011
0
        }
7012
0
    }
7013
0
    else
7014
0
    {
7015
0
        SoftRollbackTransaction();
7016
0
    }
7017
7018
0
    return eErr;
7019
0
}
7020
7021
/************************************************************************/
7022
/*                       DeleteRasterLayer()                            */
7023
/************************************************************************/
7024
7025
OGRErr GDALGeoPackageDataset::DeleteRasterLayer(const char *pszLayerName)
7026
0
{
7027
    // Temporary remove foreign key checks
7028
0
    const GPKGTemporaryForeignKeyCheckDisabler
7029
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
7030
7031
0
    OGRErr eErr = SoftStartTransaction();
7032
7033
0
    if (eErr == OGRERR_NONE)
7034
0
    {
7035
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix WHERE "
7036
0
                                       "lower(table_name) = lower('%q')",
7037
0
                                       pszLayerName);
7038
0
        eErr = SQLCommand(hDB, pszSQL);
7039
0
        sqlite3_free(pszSQL);
7040
0
    }
7041
7042
0
    if (eErr == OGRERR_NONE)
7043
0
    {
7044
0
        char *pszSQL = sqlite3_mprintf("DELETE FROM gpkg_tile_matrix_set WHERE "
7045
0
                                       "lower(table_name) = lower('%q')",
7046
0
                                       pszLayerName);
7047
0
        eErr = SQLCommand(hDB, pszSQL);
7048
0
        sqlite3_free(pszSQL);
7049
0
    }
7050
7051
0
    if (eErr == OGRERR_NONE && HasGriddedCoverageAncillaryTable())
7052
0
    {
7053
0
        char *pszSQL =
7054
0
            sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_coverage_ancillary "
7055
0
                            "WHERE lower(tile_matrix_set_name) = lower('%q')",
7056
0
                            pszLayerName);
7057
0
        eErr = SQLCommand(hDB, pszSQL);
7058
0
        sqlite3_free(pszSQL);
7059
7060
0
        if (eErr == OGRERR_NONE)
7061
0
        {
7062
0
            pszSQL =
7063
0
                sqlite3_mprintf("DELETE FROM gpkg_2d_gridded_tile_ancillary "
7064
0
                                "WHERE lower(tpudt_name) = lower('%q')",
7065
0
                                pszLayerName);
7066
0
            eErr = SQLCommand(hDB, pszSQL);
7067
0
            sqlite3_free(pszSQL);
7068
0
        }
7069
0
    }
7070
7071
0
    if (eErr == OGRERR_NONE)
7072
0
    {
7073
0
        eErr = DeleteLayerCommon(pszLayerName);
7074
0
    }
7075
7076
0
    if (eErr == OGRERR_NONE)
7077
0
    {
7078
0
        eErr = SoftCommitTransaction();
7079
0
    }
7080
0
    else
7081
0
    {
7082
0
        SoftRollbackTransaction();
7083
0
    }
7084
7085
0
    return eErr;
7086
0
}
7087
7088
/************************************************************************/
7089
/*                    DeleteVectorOrRasterLayer()                       */
7090
/************************************************************************/
7091
7092
bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName)
7093
0
{
7094
7095
0
    int idx = FindLayerIndex(pszLayerName);
7096
0
    if (idx >= 0)
7097
0
    {
7098
0
        DeleteLayer(idx);
7099
0
        return true;
7100
0
    }
7101
7102
0
    char *pszSQL =
7103
0
        sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7104
0
                        "lower(table_name) = lower('%q') "
7105
0
                        "AND data_type IN ('tiles', '2d-gridded-coverage')",
7106
0
                        pszLayerName);
7107
0
    bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7108
0
    sqlite3_free(pszSQL);
7109
0
    if (bIsRasterTable)
7110
0
    {
7111
0
        DeleteRasterLayer(pszLayerName);
7112
0
        return true;
7113
0
    }
7114
0
    return false;
7115
0
}
7116
7117
bool GDALGeoPackageDataset::RenameVectorOrRasterLayer(
7118
    const char *pszLayerName, const char *pszNewLayerName)
7119
0
{
7120
0
    int idx = FindLayerIndex(pszLayerName);
7121
0
    if (idx >= 0)
7122
0
    {
7123
0
        m_apoLayers[idx]->Rename(pszNewLayerName);
7124
0
        return true;
7125
0
    }
7126
7127
0
    char *pszSQL =
7128
0
        sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE "
7129
0
                        "lower(table_name) = lower('%q') "
7130
0
                        "AND data_type IN ('tiles', '2d-gridded-coverage')",
7131
0
                        pszLayerName);
7132
0
    const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1;
7133
0
    sqlite3_free(pszSQL);
7134
7135
0
    if (bIsRasterTable)
7136
0
    {
7137
0
        return RenameRasterLayer(pszLayerName, pszNewLayerName);
7138
0
    }
7139
7140
0
    return false;
7141
0
}
7142
7143
bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName,
7144
                                              const char *pszNewLayerName)
7145
0
{
7146
0
    std::string osSQL;
7147
7148
0
    char *pszSQL = sqlite3_mprintf(
7149
0
        "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') "
7150
0
        "AND type IN ('table', 'view')",
7151
0
        pszNewLayerName);
7152
0
    const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1;
7153
0
    sqlite3_free(pszSQL);
7154
0
    if (bAlreadyExists)
7155
0
    {
7156
0
        CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
7157
0
                 pszNewLayerName);
7158
0
        return false;
7159
0
    }
7160
7161
    // Temporary remove foreign key checks
7162
0
    const GPKGTemporaryForeignKeyCheckDisabler
7163
0
        oGPKGTemporaryForeignKeyCheckDisabler(this);
7164
7165
0
    if (SoftStartTransaction() != OGRERR_NONE)
7166
0
    {
7167
0
        return false;
7168
0
    }
7169
7170
0
    pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE "
7171
0
                             "lower(table_name) = lower('%q');",
7172
0
                             pszNewLayerName, pszLayerName);
7173
0
    osSQL = pszSQL;
7174
0
    sqlite3_free(pszSQL);
7175
7176
0
    pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE "
7177
0
                             "lower(identifier) = lower('%q');",
7178
0
                             pszNewLayerName, pszLayerName);
7179
0
    osSQL += pszSQL;
7180
0
    sqlite3_free(pszSQL);
7181
7182
0
    pszSQL =
7183
0
        sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE "
7184
0
                        "lower(table_name) = lower('%q');",
7185
0
                        pszNewLayerName, pszLayerName);
7186
0
    osSQL += pszSQL;
7187
0
    sqlite3_free(pszSQL);
7188
7189
0
    pszSQL = sqlite3_mprintf(
7190
0
        "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE "
7191
0
        "lower(table_name) = lower('%q');",
7192
0
        pszNewLayerName, pszLayerName);
7193
0
    osSQL += pszSQL;
7194
0
    sqlite3_free(pszSQL);
7195
7196
0
    if (HasGriddedCoverageAncillaryTable())
7197
0
    {
7198
0
        pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary "
7199
0
                                 "SET tile_matrix_set_name = '%q' WHERE "
7200
0
                                 "lower(tile_matrix_set_name) = lower('%q');",
7201
0
                                 pszNewLayerName, pszLayerName);
7202
0
        osSQL += pszSQL;
7203
0
        sqlite3_free(pszSQL);
7204
7205
0
        pszSQL = sqlite3_mprintf(
7206
0
            "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE "
7207
0
            "lower(tpudt_name) = lower('%q');",
7208
0
            pszNewLayerName, pszLayerName);
7209
0
        osSQL += pszSQL;
7210
0
        sqlite3_free(pszSQL);
7211
0
    }
7212
7213
0
    if (HasExtensionsTable())
7214
0
    {
7215
0
        pszSQL = sqlite3_mprintf(
7216
0
            "UPDATE gpkg_extensions SET table_name = '%q' WHERE "
7217
0
            "lower(table_name) = lower('%q');",
7218
0
            pszNewLayerName, pszLayerName);
7219
0
        osSQL += pszSQL;
7220
0
        sqlite3_free(pszSQL);
7221
0
    }
7222
7223
0
    if (HasMetadataTables())
7224
0
    {
7225
0
        pszSQL = sqlite3_mprintf(
7226
0
            "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE "
7227
0
            "lower(table_name) = lower('%q');",
7228
0
            pszNewLayerName, pszLayerName);
7229
0
        osSQL += pszSQL;
7230
0
        sqlite3_free(pszSQL);
7231
0
    }
7232
7233
0
    if (HasDataColumnsTable())
7234
0
    {
7235
0
        pszSQL = sqlite3_mprintf(
7236
0
            "UPDATE gpkg_data_columns SET table_name = '%q' WHERE "
7237
0
            "lower(table_name) = lower('%q');",
7238
0
            pszNewLayerName, pszLayerName);
7239
0
        osSQL += pszSQL;
7240
0
        sqlite3_free(pszSQL);
7241
0
    }
7242
7243
0
    if (HasQGISLayerStyles())
7244
0
    {
7245
        // Update QGIS styles
7246
0
        pszSQL =
7247
0
            sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE "
7248
0
                            "lower(f_table_name) = lower('%q');",
7249
0
                            pszNewLayerName, pszLayerName);
7250
0
        osSQL += pszSQL;
7251
0
        sqlite3_free(pszSQL);
7252
0
    }
7253
7254
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
7255
0
    if (m_bHasGPKGOGRContents)
7256
0
    {
7257
0
        pszSQL = sqlite3_mprintf(
7258
0
            "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE "
7259
0
            "lower(table_name) = lower('%q');",
7260
0
            pszNewLayerName, pszLayerName);
7261
0
        osSQL += pszSQL;
7262
0
        sqlite3_free(pszSQL);
7263
0
    }
7264
0
#endif
7265
7266
0
    if (HasGpkgextRelationsTable())
7267
0
    {
7268
0
        pszSQL = sqlite3_mprintf(
7269
0
            "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE "
7270
0
            "lower(base_table_name) = lower('%q');",
7271
0
            pszNewLayerName, pszLayerName);
7272
0
        osSQL += pszSQL;
7273
0
        sqlite3_free(pszSQL);
7274
7275
0
        pszSQL = sqlite3_mprintf(
7276
0
            "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE "
7277
0
            "lower(related_table_name) = lower('%q');",
7278
0
            pszNewLayerName, pszLayerName);
7279
0
        osSQL += pszSQL;
7280
0
        sqlite3_free(pszSQL);
7281
7282
0
        pszSQL = sqlite3_mprintf(
7283
0
            "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE "
7284
0
            "lower(mapping_table_name) = lower('%q');",
7285
0
            pszNewLayerName, pszLayerName);
7286
0
        osSQL += pszSQL;
7287
0
        sqlite3_free(pszSQL);
7288
0
    }
7289
7290
    // Drop all triggers for the layer
7291
0
    pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = "
7292
0
                             "'trigger' AND tbl_name = '%q'",
7293
0
                             pszLayerName);
7294
0
    auto oTriggerResult = SQLQuery(GetDB(), pszSQL);
7295
0
    sqlite3_free(pszSQL);
7296
0
    if (oTriggerResult)
7297
0
    {
7298
0
        for (int i = 0; i < oTriggerResult->RowCount(); i++)
7299
0
        {
7300
0
            const char *pszTriggerName = oTriggerResult->GetValue(0, i);
7301
0
            pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";",
7302
0
                                     pszTriggerName);
7303
0
            osSQL += pszSQL;
7304
0
            sqlite3_free(pszSQL);
7305
0
        }
7306
0
    }
7307
7308
0
    pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";",
7309
0
                             pszLayerName, pszNewLayerName);
7310
0
    osSQL += pszSQL;
7311
0
    sqlite3_free(pszSQL);
7312
7313
    // Recreate all zoom/tile triggers
7314
0
    if (oTriggerResult)
7315
0
    {
7316
0
        osSQL += CreateRasterTriggersSQL(pszNewLayerName);
7317
0
    }
7318
7319
0
    OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str());
7320
7321
    // Check foreign key integrity
7322
0
    if (eErr == OGRERR_NONE)
7323
0
    {
7324
0
        eErr = PragmaCheck("foreign_key_check", "", 0);
7325
0
    }
7326
7327
0
    if (eErr == OGRERR_NONE)
7328
0
    {
7329
0
        eErr = SoftCommitTransaction();
7330
0
    }
7331
0
    else
7332
0
    {
7333
0
        SoftRollbackTransaction();
7334
0
    }
7335
7336
0
    return eErr == OGRERR_NONE;
7337
0
}
7338
7339
/************************************************************************/
7340
/*                       TestCapability()                               */
7341
/************************************************************************/
7342
7343
int GDALGeoPackageDataset::TestCapability(const char *pszCap) const
7344
7.61k
{
7345
7.61k
    if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
7346
4.21k
        EQUAL(pszCap, "RenameLayer"))
7347
3.39k
    {
7348
3.39k
        return GetUpdate();
7349
3.39k
    }
7350
4.21k
    else if (EQUAL(pszCap, ODsCCurveGeometries))
7351
93
        return TRUE;
7352
4.12k
    else if (EQUAL(pszCap, ODsCMeasuredGeometries))
7353
0
        return TRUE;
7354
4.12k
    else if (EQUAL(pszCap, ODsCZGeometries))
7355
0
        return TRUE;
7356
4.12k
    else if (EQUAL(pszCap, ODsCRandomLayerWrite) ||
7357
4.12k
             EQUAL(pszCap, GDsCAddRelationship) ||
7358
4.12k
             EQUAL(pszCap, GDsCDeleteRelationship) ||
7359
4.12k
             EQUAL(pszCap, GDsCUpdateRelationship) ||
7360
4.12k
             EQUAL(pszCap, ODsCAddFieldDomain) ||
7361
4.12k
             EQUAL(pszCap, ODsCUpdateFieldDomain) ||
7362
4.12k
             EQUAL(pszCap, ODsCDeleteFieldDomain))
7363
0
    {
7364
0
        return GetUpdate();
7365
0
    }
7366
7367
4.12k
    return OGRSQLiteBaseDataSource::TestCapability(pszCap);
7368
7.61k
}
7369
7370
/************************************************************************/
7371
/*                       ResetReadingAllLayers()                        */
7372
/************************************************************************/
7373
7374
void GDALGeoPackageDataset::ResetReadingAllLayers()
7375
0
{
7376
0
    for (auto &poLayer : m_apoLayers)
7377
0
    {
7378
0
        poLayer->ResetReading();
7379
0
    }
7380
0
}
7381
7382
/************************************************************************/
7383
/*                             ExecuteSQL()                             */
7384
/************************************************************************/
7385
7386
static const char *const apszFuncsWithSideEffects[] = {
7387
    "CreateSpatialIndex",
7388
    "DisableSpatialIndex",
7389
    "HasSpatialIndex",
7390
    "RegisterGeometryExtension",
7391
};
7392
7393
OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand,
7394
                                            OGRGeometry *poSpatialFilter,
7395
                                            const char *pszDialect)
7396
7397
0
{
7398
0
    m_bHasReadMetadataFromStorage = false;
7399
7400
0
    FlushMetadata();
7401
7402
0
    while (*pszSQLCommand != '\0' &&
7403
0
           isspace(static_cast<unsigned char>(*pszSQLCommand)))
7404
0
        pszSQLCommand++;
7405
7406
0
    CPLString osSQLCommand(pszSQLCommand);
7407
0
    if (!osSQLCommand.empty() && osSQLCommand.back() == ';')
7408
0
        osSQLCommand.pop_back();
7409
7410
0
    if (osSQLCommand.ifind("AsGPB(ST_") != std::string::npos ||
7411
0
        osSQLCommand.ifind("AsGPB( ST_") != std::string::npos)
7412
0
    {
7413
0
        CPLError(CE_Warning, CPLE_AppDefined,
7414
0
                 "Use of AsGPB(ST_xxx(...)) found in \"%s\". Since GDAL 3.13, "
7415
0
                 "ST_xxx() functions return a GeoPackage geometry when used "
7416
0
                 "with a GeoPackage connection, and the use of AsGPB() is no "
7417
0
                 "longer needed. It is here automatically removed",
7418
0
                 osSQLCommand.c_str());
7419
0
        osSQLCommand.replaceAll("AsGPB(ST_", "(ST_");
7420
0
        osSQLCommand.replaceAll("AsGPB( ST_", "(ST_");
7421
0
    }
7422
7423
0
    if (pszDialect == nullptr || !EQUAL(pszDialect, "DEBUG"))
7424
0
    {
7425
        // Some SQL commands will influence the feature count behind our
7426
        // back, so disable it in that case.
7427
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
7428
0
        const bool bInsertOrDelete =
7429
0
            osSQLCommand.ifind("insert into ") != std::string::npos ||
7430
0
            osSQLCommand.ifind("insert or replace into ") !=
7431
0
                std::string::npos ||
7432
0
            osSQLCommand.ifind("delete from ") != std::string::npos;
7433
0
        const bool bRollback =
7434
0
            osSQLCommand.ifind("rollback ") != std::string::npos;
7435
0
#endif
7436
7437
0
        for (auto &poLayer : m_apoLayers)
7438
0
        {
7439
0
            if (poLayer->SyncToDisk() != OGRERR_NONE)
7440
0
                return nullptr;
7441
0
#ifdef ENABLE_GPKG_OGR_CONTENTS
7442
0
            if (bRollback ||
7443
0
                (bInsertOrDelete &&
7444
0
                 osSQLCommand.ifind(poLayer->GetName()) != std::string::npos))
7445
0
            {
7446
0
                poLayer->DisableFeatureCount();
7447
0
            }
7448
0
#endif
7449
0
        }
7450
0
    }
7451
7452
0
    if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 0") ||
7453
0
        EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=0") ||
7454
0
        EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =0") ||
7455
0
        EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 0"))
7456
0
    {
7457
0
        OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, false);
7458
0
    }
7459
0
    else if (EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like = 1") ||
7460
0
             EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like=1") ||
7461
0
             EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like =1") ||
7462
0
             EQUAL(pszSQLCommand, "PRAGMA case_sensitive_like= 1"))
7463
0
    {
7464
0
        OGRSQLiteSQLFunctionsSetCaseSensitiveLike(m_pSQLFunctionData, true);
7465
0
    }
7466
7467
    /* -------------------------------------------------------------------- */
7468
    /*      DEBUG "SELECT nolock" command.                                  */
7469
    /* -------------------------------------------------------------------- */
7470
0
    if (pszDialect != nullptr && EQUAL(pszDialect, "DEBUG") &&
7471
0
        EQUAL(osSQLCommand, "SELECT nolock"))
7472
0
    {
7473
0
        return new OGRSQLiteSingleFeatureLayer(osSQLCommand, m_bNoLock ? 1 : 0);
7474
0
    }
7475
7476
    /* -------------------------------------------------------------------- */
7477
    /*      Special case DELLAYER: command.                                 */
7478
    /* -------------------------------------------------------------------- */
7479
0
    if (STARTS_WITH_CI(osSQLCommand, "DELLAYER:"))
7480
0
    {
7481
0
        const char *pszLayerName = osSQLCommand.c_str() + strlen("DELLAYER:");
7482
7483
0
        while (*pszLayerName == ' ')
7484
0
            pszLayerName++;
7485
7486
0
        if (!DeleteVectorOrRasterLayer(pszLayerName))
7487
0
        {
7488
0
            CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7489
0
                     pszLayerName);
7490
0
        }
7491
0
        return nullptr;
7492
0
    }
7493
7494
    /* -------------------------------------------------------------------- */
7495
    /*      Special case RECOMPUTE EXTENT ON command.                       */
7496
    /* -------------------------------------------------------------------- */
7497
0
    if (STARTS_WITH_CI(osSQLCommand, "RECOMPUTE EXTENT ON "))
7498
0
    {
7499
0
        const char *pszLayerName =
7500
0
            osSQLCommand.c_str() + strlen("RECOMPUTE EXTENT ON ");
7501
7502
0
        while (*pszLayerName == ' ')
7503
0
            pszLayerName++;
7504
7505
0
        int idx = FindLayerIndex(pszLayerName);
7506
0
        if (idx >= 0)
7507
0
        {
7508
0
            m_apoLayers[idx]->RecomputeExtent();
7509
0
        }
7510
0
        else
7511
0
            CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer: %s",
7512
0
                     pszLayerName);
7513
0
        return nullptr;
7514
0
    }
7515
7516
    /* -------------------------------------------------------------------- */
7517
    /*      Intercept DROP TABLE                                            */
7518
    /* -------------------------------------------------------------------- */
7519
0
    if (STARTS_WITH_CI(osSQLCommand, "DROP TABLE "))
7520
0
    {
7521
0
        const char *pszLayerName = osSQLCommand.c_str() + strlen("DROP TABLE ");
7522
7523
0
        while (*pszLayerName == ' ')
7524
0
            pszLayerName++;
7525
7526
0
        if (DeleteVectorOrRasterLayer(SQLUnescape(pszLayerName)))
7527
0
            return nullptr;
7528
0
    }
7529
7530
    /* -------------------------------------------------------------------- */
7531
    /*      Intercept ALTER TABLE src_table RENAME TO dst_table             */
7532
    /*      and       ALTER TABLE table RENAME COLUMN src_name TO dst_name  */
7533
    /*      and       ALTER TABLE table DROP COLUMN col_name                */
7534
    /*                                                                      */
7535
    /*      We do this because SQLite mechanisms can't deal with updating   */
7536
    /*      literal values in gpkg_ tables that refer to table and column   */
7537
    /*      names.                                                          */
7538
    /* -------------------------------------------------------------------- */
7539
0
    if (STARTS_WITH_CI(osSQLCommand, "ALTER TABLE "))
7540
0
    {
7541
0
        char **papszTokens = SQLTokenize(osSQLCommand);
7542
        /* ALTER TABLE src_table RENAME TO dst_table */
7543
0
        if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "RENAME") &&
7544
0
            EQUAL(papszTokens[4], "TO"))
7545
0
        {
7546
0
            const char *pszSrcTableName = papszTokens[2];
7547
0
            const char *pszDstTableName = papszTokens[5];
7548
0
            if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName),
7549
0
                                          SQLUnescape(pszDstTableName)))
7550
0
            {
7551
0
                CSLDestroy(papszTokens);
7552
0
                return nullptr;
7553
0
            }
7554
0
        }
7555
        /* ALTER TABLE table RENAME COLUMN src_name TO dst_name */
7556
0
        else if (CSLCount(papszTokens) == 8 &&
7557
0
                 EQUAL(papszTokens[3], "RENAME") &&
7558
0
                 EQUAL(papszTokens[4], "COLUMN") && EQUAL(papszTokens[6], "TO"))
7559
0
        {
7560
0
            const char *pszTableName = papszTokens[2];
7561
0
            const char *pszSrcColumn = papszTokens[5];
7562
0
            const char *pszDstColumn = papszTokens[7];
7563
0
            OGRGeoPackageTableLayer *poLayer =
7564
0
                dynamic_cast<OGRGeoPackageTableLayer *>(
7565
0
                    GetLayerByName(SQLUnescape(pszTableName)));
7566
0
            if (poLayer)
7567
0
            {
7568
0
                int nSrcFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7569
0
                    SQLUnescape(pszSrcColumn));
7570
0
                if (nSrcFieldIdx >= 0)
7571
0
                {
7572
                    // OFTString or any type will do as we just alter the name
7573
                    // so it will be ignored.
7574
0
                    OGRFieldDefn oFieldDefn(SQLUnescape(pszDstColumn),
7575
0
                                            OFTString);
7576
0
                    poLayer->AlterFieldDefn(nSrcFieldIdx, &oFieldDefn,
7577
0
                                            ALTER_NAME_FLAG);
7578
0
                    CSLDestroy(papszTokens);
7579
0
                    return nullptr;
7580
0
                }
7581
0
            }
7582
0
        }
7583
        /* ALTER TABLE table DROP COLUMN col_name */
7584
0
        else if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[3], "DROP") &&
7585
0
                 EQUAL(papszTokens[4], "COLUMN"))
7586
0
        {
7587
0
            const char *pszTableName = papszTokens[2];
7588
0
            const char *pszColumnName = papszTokens[5];
7589
0
            OGRGeoPackageTableLayer *poLayer =
7590
0
                dynamic_cast<OGRGeoPackageTableLayer *>(
7591
0
                    GetLayerByName(SQLUnescape(pszTableName)));
7592
0
            if (poLayer)
7593
0
            {
7594
0
                int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(
7595
0
                    SQLUnescape(pszColumnName));
7596
0
                if (nFieldIdx >= 0)
7597
0
                {
7598
0
                    poLayer->DeleteField(nFieldIdx);
7599
0
                    CSLDestroy(papszTokens);
7600
0
                    return nullptr;
7601
0
                }
7602
0
            }
7603
0
        }
7604
0
        CSLDestroy(papszTokens);
7605
0
    }
7606
7607
0
    if (ProcessTransactionSQL(osSQLCommand))
7608
0
    {
7609
0
        return nullptr;
7610
0
    }
7611
7612
0
    if (EQUAL(osSQLCommand, "VACUUM"))
7613
0
    {
7614
0
        ResetReadingAllLayers();
7615
0
    }
7616
0
    else if (STARTS_WITH_CI(osSQLCommand, "DELETE FROM "))
7617
0
    {
7618
        // Optimize truncation of a table, especially if it has a spatial
7619
        // index.
7620
0
        const CPLStringList aosTokens(SQLTokenize(osSQLCommand));
7621
0
        if (aosTokens.size() == 3)
7622
0
        {
7623
0
            const char *pszTableName = aosTokens[2];
7624
0
            OGRGeoPackageTableLayer *poLayer =
7625
0
                dynamic_cast<OGRGeoPackageTableLayer *>(
7626
0
                    GetLayerByName(SQLUnescape(pszTableName)));
7627
0
            if (poLayer)
7628
0
            {
7629
0
                poLayer->Truncate();
7630
0
                return nullptr;
7631
0
            }
7632
0
        }
7633
0
    }
7634
0
    else if (pszDialect != nullptr && EQUAL(pszDialect, "INDIRECT_SQLITE"))
7635
0
        return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter, "SQLITE");
7636
0
    else if (pszDialect != nullptr && !EQUAL(pszDialect, "") &&
7637
0
             !EQUAL(pszDialect, "NATIVE") && !EQUAL(pszDialect, "SQLITE") &&
7638
0
             !EQUAL(pszDialect, "DEBUG"))
7639
0
        return GDALDataset::ExecuteSQL(osSQLCommand, poSpatialFilter,
7640
0
                                       pszDialect);
7641
7642
    /* -------------------------------------------------------------------- */
7643
    /*      Prepare statement.                                              */
7644
    /* -------------------------------------------------------------------- */
7645
0
    sqlite3_stmt *hSQLStmt = nullptr;
7646
7647
    /* This will speed-up layer creation */
7648
    /* ORDER BY are costly to evaluate and are not necessary to establish */
7649
    /* the layer definition. */
7650
0
    bool bUseStatementForGetNextFeature = true;
7651
0
    bool bEmptyLayer = false;
7652
0
    CPLString osSQLCommandTruncated(osSQLCommand);
7653
7654
0
    if (osSQLCommand.ifind("SELECT ") == 0 &&
7655
0
        CPLString(osSQLCommand.substr(1)).ifind("SELECT ") ==
7656
0
            std::string::npos &&
7657
0
        osSQLCommand.ifind(" UNION ") == std::string::npos &&
7658
0
        osSQLCommand.ifind(" INTERSECT ") == std::string::npos &&
7659
0
        osSQLCommand.ifind(" EXCEPT ") == std::string::npos)
7660
0
    {
7661
0
        size_t nOrderByPos = osSQLCommand.ifind(" ORDER BY ");
7662
0
        if (nOrderByPos != std::string::npos)
7663
0
        {
7664
0
            osSQLCommandTruncated.resize(nOrderByPos);
7665
0
            bUseStatementForGetNextFeature = false;
7666
0
        }
7667
0
    }
7668
7669
0
    int rc = prepareSql(hDB, osSQLCommandTruncated.c_str(),
7670
0
                        static_cast<int>(osSQLCommandTruncated.size()),
7671
0
                        &hSQLStmt, nullptr);
7672
7673
0
    if (rc != SQLITE_OK)
7674
0
    {
7675
0
        CPLError(CE_Failure, CPLE_AppDefined,
7676
0
                 "In ExecuteSQL(): sqlite3_prepare_v2(%s): %s",
7677
0
                 osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7678
7679
0
        if (hSQLStmt != nullptr)
7680
0
        {
7681
0
            sqlite3_finalize(hSQLStmt);
7682
0
        }
7683
7684
0
        return nullptr;
7685
0
    }
7686
7687
    /* -------------------------------------------------------------------- */
7688
    /*      Do we get a resultset?                                          */
7689
    /* -------------------------------------------------------------------- */
7690
0
    rc = sqlite3_step(hSQLStmt);
7691
7692
0
    for (auto &poLayer : m_apoLayers)
7693
0
    {
7694
0
        poLayer->RunDeferredDropRTreeTableIfNecessary();
7695
0
    }
7696
7697
0
    if (rc != SQLITE_ROW)
7698
0
    {
7699
0
        if (rc != SQLITE_DONE)
7700
0
        {
7701
0
            CPLError(CE_Failure, CPLE_AppDefined,
7702
0
                     "In ExecuteSQL(): sqlite3_step(%s):\n  %s",
7703
0
                     osSQLCommandTruncated.c_str(), sqlite3_errmsg(hDB));
7704
7705
0
            sqlite3_finalize(hSQLStmt);
7706
0
            return nullptr;
7707
0
        }
7708
7709
0
        if (EQUAL(osSQLCommand, "VACUUM"))
7710
0
        {
7711
0
            sqlite3_finalize(hSQLStmt);
7712
            /* VACUUM rewrites the DB, so we need to reset the application id */
7713
0
            SetApplicationAndUserVersionId();
7714
0
            return nullptr;
7715
0
        }
7716
7717
0
        if (!STARTS_WITH_CI(osSQLCommand, "SELECT "))
7718
0
        {
7719
0
            sqlite3_finalize(hSQLStmt);
7720
0
            return nullptr;
7721
0
        }
7722
7723
0
        bUseStatementForGetNextFeature = false;
7724
0
        bEmptyLayer = true;
7725
0
    }
7726
7727
    /* -------------------------------------------------------------------- */
7728
    /*      Special case for some functions which must be run               */
7729
    /*      only once                                                       */
7730
    /* -------------------------------------------------------------------- */
7731
0
    if (STARTS_WITH_CI(osSQLCommand, "SELECT "))
7732
0
    {
7733
0
        for (unsigned int i = 0; i < sizeof(apszFuncsWithSideEffects) /
7734
0
                                         sizeof(apszFuncsWithSideEffects[0]);
7735
0
             i++)
7736
0
        {
7737
0
            if (EQUALN(apszFuncsWithSideEffects[i], osSQLCommand.c_str() + 7,
7738
0
                       strlen(apszFuncsWithSideEffects[i])))
7739
0
            {
7740
0
                if (sqlite3_column_count(hSQLStmt) == 1 &&
7741
0
                    sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7742
0
                {
7743
0
                    int ret = sqlite3_column_int(hSQLStmt, 0);
7744
7745
0
                    sqlite3_finalize(hSQLStmt);
7746
7747
0
                    return new OGRSQLiteSingleFeatureLayer(
7748
0
                        apszFuncsWithSideEffects[i], ret);
7749
0
                }
7750
0
            }
7751
0
        }
7752
0
    }
7753
0
    else if (STARTS_WITH_CI(osSQLCommand, "PRAGMA "))
7754
0
    {
7755
0
        if (sqlite3_column_count(hSQLStmt) == 1 &&
7756
0
            sqlite3_column_type(hSQLStmt, 0) == SQLITE_INTEGER)
7757
0
        {
7758
0
            int ret = sqlite3_column_int(hSQLStmt, 0);
7759
7760
0
            sqlite3_finalize(hSQLStmt);
7761
7762
0
            return new OGRSQLiteSingleFeatureLayer(osSQLCommand.c_str() + 7,
7763
0
                                                   ret);
7764
0
        }
7765
0
        else if (sqlite3_column_count(hSQLStmt) == 1 &&
7766
0
                 sqlite3_column_type(hSQLStmt, 0) == SQLITE_TEXT)
7767
0
        {
7768
0
            const char *pszRet = reinterpret_cast<const char *>(
7769
0
                sqlite3_column_text(hSQLStmt, 0));
7770
7771
0
            OGRLayer *poRet = new OGRSQLiteSingleFeatureLayer(
7772
0
                osSQLCommand.c_str() + 7, pszRet);
7773
7774
0
            sqlite3_finalize(hSQLStmt);
7775
7776
0
            return poRet;
7777
0
        }
7778
0
    }
7779
7780
    /* -------------------------------------------------------------------- */
7781
    /*      Create layer.                                                   */
7782
    /* -------------------------------------------------------------------- */
7783
7784
0
    auto poLayer = std::make_unique<OGRGeoPackageSelectLayer>(
7785
0
        this, osSQLCommand, hSQLStmt, bUseStatementForGetNextFeature,
7786
0
        bEmptyLayer);
7787
7788
0
    if (poSpatialFilter != nullptr &&
7789
0
        poLayer->GetLayerDefn()->GetGeomFieldCount() > 0)
7790
0
        poLayer->SetSpatialFilter(0, poSpatialFilter);
7791
7792
0
    return poLayer.release();
7793
0
}
7794
7795
/************************************************************************/
7796
/*                          ReleaseResultSet()                          */
7797
/************************************************************************/
7798
7799
void GDALGeoPackageDataset::ReleaseResultSet(OGRLayer *poLayer)
7800
7801
0
{
7802
0
    delete poLayer;
7803
0
}
7804
7805
/************************************************************************/
7806
/*                         HasExtensionsTable()                         */
7807
/************************************************************************/
7808
7809
bool GDALGeoPackageDataset::HasExtensionsTable()
7810
4.30k
{
7811
4.30k
    return SQLGetInteger(
7812
4.30k
               hDB,
7813
4.30k
               "SELECT 1 FROM sqlite_master WHERE name = 'gpkg_extensions' "
7814
4.30k
               "AND type IN ('table', 'view')",
7815
4.30k
               nullptr) == 1;
7816
4.30k
}
7817
7818
/************************************************************************/
7819
/*                    CheckUnknownExtensions()                          */
7820
/************************************************************************/
7821
7822
void GDALGeoPackageDataset::CheckUnknownExtensions(bool bCheckRasterTable)
7823
649
{
7824
649
    if (!HasExtensionsTable())
7825
310
        return;
7826
7827
339
    char *pszSQL = nullptr;
7828
339
    if (!bCheckRasterTable)
7829
335
        pszSQL = sqlite3_mprintf(
7830
335
            "SELECT extension_name, definition, scope FROM gpkg_extensions "
7831
335
            "WHERE (table_name IS NULL "
7832
335
            "AND extension_name IS NOT NULL "
7833
335
            "AND definition IS NOT NULL "
7834
335
            "AND scope IS NOT NULL "
7835
335
            "AND extension_name NOT IN ("
7836
335
            "'gdal_aspatial', "
7837
335
            "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
7838
335
            "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
7839
                                       // 17-066r1 finalization
7840
335
            "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
7841
335
            "'gpkg_metadata', "
7842
335
            "'gpkg_schema', "
7843
335
            "'gpkg_crs_wkt', "
7844
335
            "'gpkg_crs_wkt_1_1', "
7845
335
            "'related_tables', 'gpkg_related_tables')) "
7846
#ifdef WORKAROUND_SQLITE3_BUGS
7847
            "OR 0 "
7848
#endif
7849
335
            "LIMIT 1000");
7850
4
    else
7851
4
        pszSQL = sqlite3_mprintf(
7852
4
            "SELECT extension_name, definition, scope FROM gpkg_extensions "
7853
4
            "WHERE (lower(table_name) = lower('%q') "
7854
4
            "AND extension_name IS NOT NULL "
7855
4
            "AND definition IS NOT NULL "
7856
4
            "AND scope IS NOT NULL "
7857
4
            "AND extension_name NOT IN ("
7858
4
            "'gpkg_elevation_tiles', "  // Old name before GPKG 1.2 approval
7859
4
            "'2d_gridded_coverage', "  // Old name after GPKG 1.2 and before OGC
7860
                                       // 17-066r1 finalization
7861
4
            "'gpkg_2d_gridded_coverage', "  // Name in OGC 17-066r1 final
7862
4
            "'gpkg_metadata', "
7863
4
            "'gpkg_schema', "
7864
4
            "'gpkg_crs_wkt', "
7865
4
            "'gpkg_crs_wkt_1_1', "
7866
4
            "'related_tables', 'gpkg_related_tables')) "
7867
#ifdef WORKAROUND_SQLITE3_BUGS
7868
            "OR 0 "
7869
#endif
7870
4
            "LIMIT 1000",
7871
4
            m_osRasterTable.c_str());
7872
7873
339
    auto oResultTable = SQLQuery(GetDB(), pszSQL);
7874
339
    sqlite3_free(pszSQL);
7875
339
    if (oResultTable && oResultTable->RowCount() > 0)
7876
0
    {
7877
0
        for (int i = 0; i < oResultTable->RowCount(); i++)
7878
0
        {
7879
0
            const char *pszExtName = oResultTable->GetValue(0, i);
7880
0
            const char *pszDefinition = oResultTable->GetValue(1, i);
7881
0
            const char *pszScope = oResultTable->GetValue(2, i);
7882
0
            if (pszExtName == nullptr || pszDefinition == nullptr ||
7883
0
                pszScope == nullptr)
7884
0
            {
7885
0
                continue;
7886
0
            }
7887
7888
0
            if (EQUAL(pszExtName, "gpkg_webp"))
7889
0
            {
7890
0
                if (GDALGetDriverByName("WEBP") == nullptr)
7891
0
                {
7892
0
                    CPLError(
7893
0
                        CE_Warning, CPLE_AppDefined,
7894
0
                        "Table %s contains WEBP tiles, but GDAL configured "
7895
0
                        "without WEBP support. Data will be missing",
7896
0
                        m_osRasterTable.c_str());
7897
0
                }
7898
0
                m_eTF = GPKG_TF_WEBP;
7899
0
                continue;
7900
0
            }
7901
0
            if (EQUAL(pszExtName, "gpkg_zoom_other"))
7902
0
            {
7903
0
                m_bZoomOther = true;
7904
0
                continue;
7905
0
            }
7906
7907
0
            if (GetUpdate() && EQUAL(pszScope, "write-only"))
7908
0
            {
7909
0
                CPLError(
7910
0
                    CE_Warning, CPLE_AppDefined,
7911
0
                    "Database relies on the '%s' (%s) extension that should "
7912
0
                    "be implemented for safe write-support, but is not "
7913
0
                    "currently. "
7914
0
                    "Update of that database are strongly discouraged to avoid "
7915
0
                    "corruption.",
7916
0
                    pszExtName, pszDefinition);
7917
0
            }
7918
0
            else if (GetUpdate() && EQUAL(pszScope, "read-write"))
7919
0
            {
7920
0
                CPLError(
7921
0
                    CE_Warning, CPLE_AppDefined,
7922
0
                    "Database relies on the '%s' (%s) extension that should "
7923
0
                    "be implemented in order to read/write it safely, but is "
7924
0
                    "not currently. "
7925
0
                    "Some data may be missing while reading that database, and "
7926
0
                    "updates are strongly discouraged.",
7927
0
                    pszExtName, pszDefinition);
7928
0
            }
7929
0
            else if (EQUAL(pszScope, "read-write") &&
7930
                     // None of the NGA extensions at
7931
                     // http://ngageoint.github.io/GeoPackage/docs/extensions/
7932
                     // affect read-only scenarios
7933
0
                     !STARTS_WITH(pszExtName, "nga_"))
7934
0
            {
7935
0
                CPLError(
7936
0
                    CE_Warning, CPLE_AppDefined,
7937
0
                    "Database relies on the '%s' (%s) extension that should "
7938
0
                    "be implemented in order to read it safely, but is not "
7939
0
                    "currently. "
7940
0
                    "Some data may be missing while reading that database.",
7941
0
                    pszExtName, pszDefinition);
7942
0
            }
7943
0
        }
7944
0
    }
7945
339
}
7946
7947
/************************************************************************/
7948
/*                         HasGDALAspatialExtension()                       */
7949
/************************************************************************/
7950
7951
bool GDALGeoPackageDataset::HasGDALAspatialExtension()
7952
585
{
7953
585
    if (!HasExtensionsTable())
7954
260
        return false;
7955
7956
325
    auto oResultTable = SQLQuery(hDB, "SELECT * FROM gpkg_extensions "
7957
325
                                      "WHERE (extension_name = 'gdal_aspatial' "
7958
325
                                      "AND table_name IS NULL "
7959
325
                                      "AND column_name IS NULL)"
7960
#ifdef WORKAROUND_SQLITE3_BUGS
7961
                                      " OR 0"
7962
#endif
7963
325
    );
7964
325
    bool bHasExtension = (oResultTable && oResultTable->RowCount() == 1);
7965
325
    return bHasExtension;
7966
585
}
7967
7968
std::string
7969
GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName)
7970
0
{
7971
0
    char *pszSQL;
7972
0
    std::string osSQL;
7973
    /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger
7974
     * Definition SQL  */
7975
0
    pszSQL = sqlite3_mprintf(
7976
0
        "CREATE TRIGGER \"%w_zoom_insert\" "
7977
0
        "BEFORE INSERT ON \"%w\" "
7978
0
        "FOR EACH ROW BEGIN "
7979
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7980
0
        "constraint: zoom_level not specified for table in "
7981
0
        "gpkg_tile_matrix') "
7982
0
        "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7983
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7984
0
        "END; "
7985
0
        "CREATE TRIGGER \"%w_zoom_update\" "
7986
0
        "BEFORE UPDATE OF zoom_level ON \"%w\" "
7987
0
        "FOR EACH ROW BEGIN "
7988
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
7989
0
        "constraint: zoom_level not specified for table in "
7990
0
        "gpkg_tile_matrix') "
7991
0
        "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM "
7992
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; "
7993
0
        "END; "
7994
0
        "CREATE TRIGGER \"%w_tile_column_insert\" "
7995
0
        "BEFORE INSERT ON \"%w\" "
7996
0
        "FOR EACH ROW BEGIN "
7997
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
7998
0
        "constraint: tile_column cannot be < 0') "
7999
0
        "WHERE (NEW.tile_column < 0) ; "
8000
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8001
0
        "constraint: tile_column must by < matrix_width specified for "
8002
0
        "table and zoom level in gpkg_tile_matrix') "
8003
0
        "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8004
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8005
0
        "zoom_level = NEW.zoom_level)); "
8006
0
        "END; "
8007
0
        "CREATE TRIGGER \"%w_tile_column_update\" "
8008
0
        "BEFORE UPDATE OF tile_column ON \"%w\" "
8009
0
        "FOR EACH ROW BEGIN "
8010
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8011
0
        "constraint: tile_column cannot be < 0') "
8012
0
        "WHERE (NEW.tile_column < 0) ; "
8013
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8014
0
        "constraint: tile_column must by < matrix_width specified for "
8015
0
        "table and zoom level in gpkg_tile_matrix') "
8016
0
        "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM "
8017
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8018
0
        "zoom_level = NEW.zoom_level)); "
8019
0
        "END; "
8020
0
        "CREATE TRIGGER \"%w_tile_row_insert\" "
8021
0
        "BEFORE INSERT ON \"%w\" "
8022
0
        "FOR EACH ROW BEGIN "
8023
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8024
0
        "constraint: tile_row cannot be < 0') "
8025
0
        "WHERE (NEW.tile_row < 0) ; "
8026
0
        "SELECT RAISE(ABORT, 'insert on table ''%q'' violates "
8027
0
        "constraint: tile_row must by < matrix_height specified for "
8028
0
        "table and zoom level in gpkg_tile_matrix') "
8029
0
        "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8030
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8031
0
        "zoom_level = NEW.zoom_level)); "
8032
0
        "END; "
8033
0
        "CREATE TRIGGER \"%w_tile_row_update\" "
8034
0
        "BEFORE UPDATE OF tile_row ON \"%w\" "
8035
0
        "FOR EACH ROW BEGIN "
8036
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8037
0
        "constraint: tile_row cannot be < 0') "
8038
0
        "WHERE (NEW.tile_row < 0) ; "
8039
0
        "SELECT RAISE(ABORT, 'update on table ''%q'' violates "
8040
0
        "constraint: tile_row must by < matrix_height specified for "
8041
0
        "table and zoom level in gpkg_tile_matrix') "
8042
0
        "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM "
8043
0
        "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND "
8044
0
        "zoom_level = NEW.zoom_level)); "
8045
0
        "END; ",
8046
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8047
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8048
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8049
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8050
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8051
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8052
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8053
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8054
0
        osTableName.c_str(), osTableName.c_str(), osTableName.c_str(),
8055
0
        osTableName.c_str());
8056
0
    osSQL = pszSQL;
8057
0
    sqlite3_free(pszSQL);
8058
0
    return osSQL;
8059
0
}
8060
8061
/************************************************************************/
8062
/*                  CreateExtensionsTableIfNecessary()                  */
8063
/************************************************************************/
8064
8065
OGRErr GDALGeoPackageDataset::CreateExtensionsTableIfNecessary()
8066
1.51k
{
8067
    /* Check if the table gpkg_extensions exists */
8068
1.51k
    if (HasExtensionsTable())
8069
1.15k
        return OGRERR_NONE;
8070
8071
    /* Requirement 79 : Every extension of a GeoPackage SHALL be registered */
8072
    /* in a corresponding row in the gpkg_extensions table. The absence of a */
8073
    /* gpkg_extensions table or the absence of rows in gpkg_extensions table */
8074
    /* SHALL both indicate the absence of extensions to a GeoPackage. */
8075
354
    const char *pszCreateGpkgExtensions =
8076
354
        "CREATE TABLE gpkg_extensions ("
8077
354
        "table_name TEXT,"
8078
354
        "column_name TEXT,"
8079
354
        "extension_name TEXT NOT NULL,"
8080
354
        "definition TEXT NOT NULL,"
8081
354
        "scope TEXT NOT NULL,"
8082
354
        "CONSTRAINT ge_tce UNIQUE (table_name, column_name, extension_name)"
8083
354
        ")";
8084
8085
354
    return SQLCommand(hDB, pszCreateGpkgExtensions);
8086
1.51k
}
8087
8088
/************************************************************************/
8089
/*                    OGR_GPKG_Intersects_Spatial_Filter()              */
8090
/************************************************************************/
8091
8092
void OGR_GPKG_Intersects_Spatial_Filter(sqlite3_context *pContext, int argc,
8093
                                        sqlite3_value **argv)
8094
0
{
8095
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8096
0
    {
8097
0
        sqlite3_result_int(pContext, 0);
8098
0
        return;
8099
0
    }
8100
8101
0
    auto poLayer =
8102
0
        static_cast<OGRGeoPackageTableLayer *>(sqlite3_user_data(pContext));
8103
8104
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8105
0
    const GByte *pabyBLOB =
8106
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8107
8108
0
    GPkgHeader sHeader;
8109
0
    if (poLayer->m_bFilterIsEnvelope &&
8110
0
        OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false, 0))
8111
0
    {
8112
0
        if (sHeader.bExtentHasXY)
8113
0
        {
8114
0
            OGREnvelope sEnvelope;
8115
0
            sEnvelope.MinX = sHeader.MinX;
8116
0
            sEnvelope.MinY = sHeader.MinY;
8117
0
            sEnvelope.MaxX = sHeader.MaxX;
8118
0
            sEnvelope.MaxY = sHeader.MaxY;
8119
0
            if (poLayer->m_sFilterEnvelope.Contains(sEnvelope))
8120
0
            {
8121
0
                sqlite3_result_int(pContext, 1);
8122
0
                return;
8123
0
            }
8124
0
        }
8125
8126
        // Check if at least one point falls into the layer filter envelope
8127
        // nHeaderLen is > 0 for GeoPackage geometries
8128
0
        if (sHeader.nHeaderLen > 0 &&
8129
0
            OGRWKBIntersectsPessimistic(pabyBLOB + sHeader.nHeaderLen,
8130
0
                                        nBLOBLen - sHeader.nHeaderLen,
8131
0
                                        poLayer->m_sFilterEnvelope))
8132
0
        {
8133
0
            sqlite3_result_int(pContext, 1);
8134
0
            return;
8135
0
        }
8136
0
    }
8137
8138
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8139
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8140
0
    if (poGeom == nullptr)
8141
0
    {
8142
        // Try also spatialite geometry blobs
8143
0
        OGRGeometry *poGeomSpatialite = nullptr;
8144
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8145
0
                                              &poGeomSpatialite) != OGRERR_NONE)
8146
0
        {
8147
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8148
0
            sqlite3_result_int(pContext, 0);
8149
0
            return;
8150
0
        }
8151
0
        poGeom.reset(poGeomSpatialite);
8152
0
    }
8153
8154
0
    sqlite3_result_int(pContext, poLayer->FilterGeometry(poGeom.get()));
8155
0
}
8156
8157
/************************************************************************/
8158
/*                      OGRGeoPackageSTMinX()                           */
8159
/************************************************************************/
8160
8161
static void OGRGeoPackageSTMinX(sqlite3_context *pContext, int argc,
8162
                                sqlite3_value **argv)
8163
2.78k
{
8164
2.78k
    GPkgHeader sHeader;
8165
2.78k
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8166
331
    {
8167
331
        sqlite3_result_null(pContext);
8168
331
        return;
8169
331
    }
8170
2.45k
    sqlite3_result_double(pContext, sHeader.MinX);
8171
2.45k
}
8172
8173
/************************************************************************/
8174
/*                      OGRGeoPackageSTMinY()                           */
8175
/************************************************************************/
8176
8177
static void OGRGeoPackageSTMinY(sqlite3_context *pContext, int argc,
8178
                                sqlite3_value **argv)
8179
2.78k
{
8180
2.78k
    GPkgHeader sHeader;
8181
2.78k
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8182
331
    {
8183
331
        sqlite3_result_null(pContext);
8184
331
        return;
8185
331
    }
8186
2.45k
    sqlite3_result_double(pContext, sHeader.MinY);
8187
2.45k
}
8188
8189
/************************************************************************/
8190
/*                      OGRGeoPackageSTMaxX()                           */
8191
/************************************************************************/
8192
8193
static void OGRGeoPackageSTMaxX(sqlite3_context *pContext, int argc,
8194
                                sqlite3_value **argv)
8195
2.78k
{
8196
2.78k
    GPkgHeader sHeader;
8197
2.78k
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8198
331
    {
8199
331
        sqlite3_result_null(pContext);
8200
331
        return;
8201
331
    }
8202
2.45k
    sqlite3_result_double(pContext, sHeader.MaxX);
8203
2.45k
}
8204
8205
/************************************************************************/
8206
/*                      OGRGeoPackageSTMaxY()                           */
8207
/************************************************************************/
8208
8209
static void OGRGeoPackageSTMaxY(sqlite3_context *pContext, int argc,
8210
                                sqlite3_value **argv)
8211
2.78k
{
8212
2.78k
    GPkgHeader sHeader;
8213
2.78k
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8214
331
    {
8215
331
        sqlite3_result_null(pContext);
8216
331
        return;
8217
331
    }
8218
2.45k
    sqlite3_result_double(pContext, sHeader.MaxY);
8219
2.45k
}
8220
8221
/************************************************************************/
8222
/*                     OGRGeoPackageSTIsEmpty()                         */
8223
/************************************************************************/
8224
8225
static void OGRGeoPackageSTIsEmpty(sqlite3_context *pContext, int argc,
8226
                                   sqlite3_value **argv)
8227
4.60k
{
8228
4.60k
    GPkgHeader sHeader;
8229
4.60k
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8230
0
    {
8231
0
        sqlite3_result_null(pContext);
8232
0
        return;
8233
0
    }
8234
4.60k
    sqlite3_result_int(pContext, sHeader.bEmpty);
8235
4.60k
}
8236
8237
/************************************************************************/
8238
/*                    OGRGeoPackageSTGeometryType()                     */
8239
/************************************************************************/
8240
8241
static void OGRGeoPackageSTGeometryType(sqlite3_context *pContext, int /*argc*/,
8242
                                        sqlite3_value **argv)
8243
0
{
8244
0
    GPkgHeader sHeader;
8245
8246
0
    int nBLOBLen = sqlite3_value_bytes(argv[0]);
8247
0
    const GByte *pabyBLOB =
8248
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8249
0
    OGRwkbGeometryType eGeometryType;
8250
8251
0
    if (nBLOBLen < 8 ||
8252
0
        GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8253
0
    {
8254
0
        if (OGRSQLiteGetSpatialiteGeometryHeader(
8255
0
                pabyBLOB, nBLOBLen, nullptr, &eGeometryType, nullptr, nullptr,
8256
0
                nullptr, nullptr, nullptr) == OGRERR_NONE)
8257
0
        {
8258
0
            sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8259
0
                                SQLITE_TRANSIENT);
8260
0
            return;
8261
0
        }
8262
0
        else
8263
0
        {
8264
0
            sqlite3_result_null(pContext);
8265
0
            return;
8266
0
        }
8267
0
    }
8268
8269
0
    if (static_cast<size_t>(nBLOBLen) < sHeader.nHeaderLen + 5)
8270
0
    {
8271
0
        sqlite3_result_null(pContext);
8272
0
        return;
8273
0
    }
8274
8275
0
    OGRErr err = OGRReadWKBGeometryType(pabyBLOB + sHeader.nHeaderLen,
8276
0
                                        wkbVariantIso, &eGeometryType);
8277
0
    if (err != OGRERR_NONE)
8278
0
        sqlite3_result_null(pContext);
8279
0
    else
8280
0
        sqlite3_result_text(pContext, OGRToOGCGeomType(eGeometryType), -1,
8281
0
                            SQLITE_TRANSIENT);
8282
0
}
8283
8284
/************************************************************************/
8285
/*                 OGRGeoPackageSTEnvelopesIntersects()                 */
8286
/************************************************************************/
8287
8288
static void OGRGeoPackageSTEnvelopesIntersects(sqlite3_context *pContext,
8289
                                               int argc, sqlite3_value **argv)
8290
0
{
8291
0
    GPkgHeader sHeader;
8292
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false))
8293
0
    {
8294
0
        sqlite3_result_int(pContext, FALSE);
8295
0
        return;
8296
0
    }
8297
0
    const double dfMinX = sqlite3_value_double(argv[1]);
8298
0
    if (sHeader.MaxX < dfMinX)
8299
0
    {
8300
0
        sqlite3_result_int(pContext, FALSE);
8301
0
        return;
8302
0
    }
8303
0
    const double dfMinY = sqlite3_value_double(argv[2]);
8304
0
    if (sHeader.MaxY < dfMinY)
8305
0
    {
8306
0
        sqlite3_result_int(pContext, FALSE);
8307
0
        return;
8308
0
    }
8309
0
    const double dfMaxX = sqlite3_value_double(argv[3]);
8310
0
    if (sHeader.MinX > dfMaxX)
8311
0
    {
8312
0
        sqlite3_result_int(pContext, FALSE);
8313
0
        return;
8314
0
    }
8315
0
    const double dfMaxY = sqlite3_value_double(argv[4]);
8316
0
    sqlite3_result_int(pContext, sHeader.MinY <= dfMaxY);
8317
0
}
8318
8319
/************************************************************************/
8320
/*              OGRGeoPackageSTEnvelopesIntersectsTwoParams()           */
8321
/************************************************************************/
8322
8323
static void
8324
OGRGeoPackageSTEnvelopesIntersectsTwoParams(sqlite3_context *pContext, int argc,
8325
                                            sqlite3_value **argv)
8326
0
{
8327
0
    GPkgHeader sHeader;
8328
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, true, false, 0))
8329
0
    {
8330
0
        sqlite3_result_int(pContext, FALSE);
8331
0
        return;
8332
0
    }
8333
0
    GPkgHeader sHeader2;
8334
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader2, true, false,
8335
0
                                1))
8336
0
    {
8337
0
        sqlite3_result_int(pContext, FALSE);
8338
0
        return;
8339
0
    }
8340
0
    if (sHeader.MaxX < sHeader2.MinX)
8341
0
    {
8342
0
        sqlite3_result_int(pContext, FALSE);
8343
0
        return;
8344
0
    }
8345
0
    if (sHeader.MaxY < sHeader2.MinY)
8346
0
    {
8347
0
        sqlite3_result_int(pContext, FALSE);
8348
0
        return;
8349
0
    }
8350
0
    if (sHeader.MinX > sHeader2.MaxX)
8351
0
    {
8352
0
        sqlite3_result_int(pContext, FALSE);
8353
0
        return;
8354
0
    }
8355
0
    sqlite3_result_int(pContext, sHeader.MinY <= sHeader2.MaxY);
8356
0
}
8357
8358
/************************************************************************/
8359
/*                    OGRGeoPackageGPKGIsAssignable()                   */
8360
/************************************************************************/
8361
8362
static void OGRGeoPackageGPKGIsAssignable(sqlite3_context *pContext,
8363
                                          int /*argc*/, sqlite3_value **argv)
8364
0
{
8365
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8366
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8367
0
    {
8368
0
        sqlite3_result_int(pContext, 0);
8369
0
        return;
8370
0
    }
8371
8372
0
    const char *pszExpected =
8373
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8374
0
    const char *pszActual =
8375
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8376
0
    int bIsAssignable = OGR_GT_IsSubClassOf(OGRFromOGCGeomType(pszActual),
8377
0
                                            OGRFromOGCGeomType(pszExpected));
8378
0
    sqlite3_result_int(pContext, bIsAssignable);
8379
0
}
8380
8381
/************************************************************************/
8382
/*                     OGRGeoPackageSTSRID()                            */
8383
/************************************************************************/
8384
8385
static void OGRGeoPackageSTSRID(sqlite3_context *pContext, int argc,
8386
                                sqlite3_value **argv)
8387
0
{
8388
0
    GPkgHeader sHeader;
8389
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8390
0
    {
8391
0
        sqlite3_result_null(pContext);
8392
0
        return;
8393
0
    }
8394
0
    sqlite3_result_int(pContext, sHeader.iSrsId);
8395
0
}
8396
8397
/************************************************************************/
8398
/*                     OGRGeoPackageSetSRID()                           */
8399
/************************************************************************/
8400
8401
static void OGRGeoPackageSetSRID(sqlite3_context *pContext, int /* argc */,
8402
                                 sqlite3_value **argv)
8403
0
{
8404
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8405
0
    {
8406
0
        sqlite3_result_null(pContext);
8407
0
        return;
8408
0
    }
8409
0
    const int nDestSRID = sqlite3_value_int(argv[1]);
8410
0
    GPkgHeader sHeader;
8411
0
    int nBLOBLen = sqlite3_value_bytes(argv[0]);
8412
0
    const GByte *pabyBLOB =
8413
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8414
8415
0
    if (nBLOBLen < 8 ||
8416
0
        GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) != OGRERR_NONE)
8417
0
    {
8418
        // Try also spatialite geometry blobs
8419
0
        OGRGeometry *poGeom = nullptr;
8420
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeom) !=
8421
0
            OGRERR_NONE)
8422
0
        {
8423
0
            sqlite3_result_null(pContext);
8424
0
            return;
8425
0
        }
8426
0
        size_t nBLOBDestLen = 0;
8427
0
        GByte *pabyDestBLOB =
8428
0
            GPkgGeometryFromOGR(poGeom, nDestSRID, nullptr, &nBLOBDestLen);
8429
0
        if (!pabyDestBLOB)
8430
0
        {
8431
0
            sqlite3_result_null(pContext);
8432
0
            return;
8433
0
        }
8434
0
        sqlite3_result_blob(pContext, pabyDestBLOB,
8435
0
                            static_cast<int>(nBLOBDestLen), VSIFree);
8436
0
        return;
8437
0
    }
8438
8439
0
    GByte *pabyDestBLOB = static_cast<GByte *>(CPLMalloc(nBLOBLen));
8440
0
    memcpy(pabyDestBLOB, pabyBLOB, nBLOBLen);
8441
0
    int32_t nSRIDToSerialize = nDestSRID;
8442
0
    if (OGR_SWAP(sHeader.eByteOrder))
8443
0
        nSRIDToSerialize = CPL_SWAP32(nSRIDToSerialize);
8444
0
    memcpy(pabyDestBLOB + 4, &nSRIDToSerialize, 4);
8445
0
    sqlite3_result_blob(pContext, pabyDestBLOB, nBLOBLen, VSIFree);
8446
0
}
8447
8448
/************************************************************************/
8449
/*                   OGRGeoPackageSTMakeValid()                         */
8450
/************************************************************************/
8451
8452
static void OGRGeoPackageSTMakeValid(sqlite3_context *pContext, int argc,
8453
                                     sqlite3_value **argv)
8454
0
{
8455
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8456
0
    {
8457
0
        sqlite3_result_null(pContext);
8458
0
        return;
8459
0
    }
8460
0
    int nBLOBLen = sqlite3_value_bytes(argv[0]);
8461
0
    const GByte *pabyBLOB =
8462
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8463
8464
0
    GPkgHeader sHeader;
8465
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8466
0
    {
8467
0
        sqlite3_result_null(pContext);
8468
0
        return;
8469
0
    }
8470
8471
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8472
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8473
0
    if (poGeom == nullptr)
8474
0
    {
8475
        // Try also spatialite geometry blobs
8476
0
        OGRGeometry *poGeomPtr = nullptr;
8477
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8478
0
            OGRERR_NONE)
8479
0
        {
8480
0
            sqlite3_result_null(pContext);
8481
0
            return;
8482
0
        }
8483
0
        poGeom.reset(poGeomPtr);
8484
0
    }
8485
0
    auto poValid = std::unique_ptr<OGRGeometry>(poGeom->MakeValid());
8486
0
    if (poValid == nullptr)
8487
0
    {
8488
0
        sqlite3_result_null(pContext);
8489
0
        return;
8490
0
    }
8491
8492
0
    size_t nBLOBDestLen = 0;
8493
0
    GByte *pabyDestBLOB = GPkgGeometryFromOGR(poValid.get(), sHeader.iSrsId,
8494
0
                                              nullptr, &nBLOBDestLen);
8495
0
    if (!pabyDestBLOB)
8496
0
    {
8497
0
        sqlite3_result_null(pContext);
8498
0
        return;
8499
0
    }
8500
0
    sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8501
0
                        VSIFree);
8502
0
}
8503
8504
/************************************************************************/
8505
/*                   OGRGeoPackageSTArea()                              */
8506
/************************************************************************/
8507
8508
static void OGRGeoPackageSTArea(sqlite3_context *pContext, int /*argc*/,
8509
                                sqlite3_value **argv)
8510
0
{
8511
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8512
0
    {
8513
0
        sqlite3_result_null(pContext);
8514
0
        return;
8515
0
    }
8516
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8517
0
    const GByte *pabyBLOB =
8518
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8519
8520
0
    GPkgHeader sHeader;
8521
0
    std::unique_ptr<OGRGeometry> poGeom;
8522
0
    if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, &sHeader) == OGRERR_NONE)
8523
0
    {
8524
0
        if (sHeader.bEmpty)
8525
0
        {
8526
0
            sqlite3_result_double(pContext, 0);
8527
0
            return;
8528
0
        }
8529
0
        const GByte *pabyWkb = pabyBLOB + sHeader.nHeaderLen;
8530
0
        size_t nWKBSize = nBLOBLen - sHeader.nHeaderLen;
8531
0
        bool bNeedSwap;
8532
0
        uint32_t nType;
8533
0
        if (OGRWKBGetGeomType(pabyWkb, nWKBSize, bNeedSwap, nType))
8534
0
        {
8535
0
            if (nType == wkbPolygon || nType == wkbPolygon25D ||
8536
0
                nType == wkbPolygon + 1000 ||  // wkbPolygonZ
8537
0
                nType == wkbPolygonM || nType == wkbPolygonZM)
8538
0
            {
8539
0
                double dfArea;
8540
0
                if (OGRWKBPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8541
0
                {
8542
0
                    sqlite3_result_double(pContext, dfArea);
8543
0
                    return;
8544
0
                }
8545
0
            }
8546
0
            else if (nType == wkbMultiPolygon || nType == wkbMultiPolygon25D ||
8547
0
                     nType == wkbMultiPolygon + 1000 ||  // wkbMultiPolygonZ
8548
0
                     nType == wkbMultiPolygonM || nType == wkbMultiPolygonZM)
8549
0
            {
8550
0
                double dfArea;
8551
0
                if (OGRWKBMultiPolygonGetArea(pabyWkb, nWKBSize, dfArea))
8552
0
                {
8553
0
                    sqlite3_result_double(pContext, dfArea);
8554
0
                    return;
8555
0
                }
8556
0
            }
8557
0
        }
8558
8559
        // For curve geometries, fallback to OGRGeometry methods
8560
0
        poGeom.reset(GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8561
0
    }
8562
0
    else
8563
0
    {
8564
        // Try also spatialite geometry blobs
8565
0
        OGRGeometry *poGeomPtr = nullptr;
8566
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen, &poGeomPtr) !=
8567
0
            OGRERR_NONE)
8568
0
        {
8569
0
            sqlite3_result_null(pContext);
8570
0
            return;
8571
0
        }
8572
0
        poGeom.reset(poGeomPtr);
8573
0
    }
8574
0
    auto poSurface = dynamic_cast<OGRSurface *>(poGeom.get());
8575
0
    if (poSurface == nullptr)
8576
0
    {
8577
0
        auto poMultiSurface = dynamic_cast<OGRMultiSurface *>(poGeom.get());
8578
0
        if (poMultiSurface == nullptr)
8579
0
        {
8580
0
            sqlite3_result_double(pContext, 0);
8581
0
        }
8582
0
        else
8583
0
        {
8584
0
            sqlite3_result_double(pContext, poMultiSurface->get_Area());
8585
0
        }
8586
0
    }
8587
0
    else
8588
0
    {
8589
0
        sqlite3_result_double(pContext, poSurface->get_Area());
8590
0
    }
8591
0
}
8592
8593
/************************************************************************/
8594
/*                     OGRGeoPackageGeodesicArea()                      */
8595
/************************************************************************/
8596
8597
static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc,
8598
                                      sqlite3_value **argv)
8599
0
{
8600
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8601
0
    {
8602
0
        sqlite3_result_null(pContext);
8603
0
        return;
8604
0
    }
8605
0
    if (sqlite3_value_int(argv[1]) != 1)
8606
0
    {
8607
0
        CPLError(CE_Warning, CPLE_NotSupported,
8608
0
                 "ST_Area(geom, use_ellipsoid) is only supported for "
8609
0
                 "use_ellipsoid = 1");
8610
0
    }
8611
8612
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8613
0
    const GByte *pabyBLOB =
8614
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8615
0
    GPkgHeader sHeader;
8616
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8617
0
    {
8618
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8619
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8620
0
        return;
8621
0
    }
8622
8623
0
    GDALGeoPackageDataset *poDS =
8624
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8625
8626
0
    std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS(
8627
0
        poDS->GetSpatialRef(sHeader.iSrsId, true));
8628
0
    if (poSrcSRS == nullptr)
8629
0
    {
8630
0
        CPLError(CE_Failure, CPLE_AppDefined,
8631
0
                 "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8632
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8633
0
        return;
8634
0
    }
8635
8636
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8637
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8638
0
    if (poGeom == nullptr)
8639
0
    {
8640
        // Try also spatialite geometry blobs
8641
0
        OGRGeometry *poGeomSpatialite = nullptr;
8642
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8643
0
                                              &poGeomSpatialite) != OGRERR_NONE)
8644
0
        {
8645
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8646
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8647
0
            return;
8648
0
        }
8649
0
        poGeom.reset(poGeomSpatialite);
8650
0
    }
8651
8652
0
    poGeom->assignSpatialReference(poSrcSRS.get());
8653
0
    sqlite3_result_double(
8654
0
        pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get())));
8655
0
}
8656
8657
/************************************************************************/
8658
/*                   OGRGeoPackageLengthOrGeodesicLength()              */
8659
/************************************************************************/
8660
8661
static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext,
8662
                                                int argc, sqlite3_value **argv)
8663
0
{
8664
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
8665
0
    {
8666
0
        sqlite3_result_null(pContext);
8667
0
        return;
8668
0
    }
8669
0
    if (argc == 2 && sqlite3_value_int(argv[1]) != 1)
8670
0
    {
8671
0
        CPLError(CE_Warning, CPLE_NotSupported,
8672
0
                 "ST_Length(geom, use_ellipsoid) is only supported for "
8673
0
                 "use_ellipsoid = 1");
8674
0
    }
8675
8676
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8677
0
    const GByte *pabyBLOB =
8678
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8679
0
    GPkgHeader sHeader;
8680
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8681
0
    {
8682
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8683
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8684
0
        return;
8685
0
    }
8686
8687
0
    GDALGeoPackageDataset *poDS =
8688
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8689
8690
0
    std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSrcSRS;
8691
0
    if (argc == 2)
8692
0
    {
8693
0
        poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true);
8694
0
        if (!poSrcSRS)
8695
0
        {
8696
0
            CPLError(CE_Failure, CPLE_AppDefined,
8697
0
                     "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8698
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8699
0
            return;
8700
0
        }
8701
0
    }
8702
8703
0
    auto poGeom = std::unique_ptr<OGRGeometry>(
8704
0
        GPkgGeometryToOGR(pabyBLOB, nBLOBLen, nullptr));
8705
0
    if (poGeom == nullptr)
8706
0
    {
8707
        // Try also spatialite geometry blobs
8708
0
        OGRGeometry *poGeomSpatialite = nullptr;
8709
0
        if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8710
0
                                              &poGeomSpatialite) != OGRERR_NONE)
8711
0
        {
8712
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8713
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8714
0
            return;
8715
0
        }
8716
0
        poGeom.reset(poGeomSpatialite);
8717
0
    }
8718
8719
0
    if (argc == 2)
8720
0
        poGeom->assignSpatialReference(poSrcSRS.get());
8721
8722
0
    sqlite3_result_double(
8723
0
        pContext,
8724
0
        argc == 1 ? OGR_G_Length(OGRGeometry::ToHandle(poGeom.get()))
8725
0
                  : OGR_G_GeodesicLength(OGRGeometry::ToHandle(poGeom.get())));
8726
0
}
8727
8728
/************************************************************************/
8729
/*                      OGRGeoPackageTransform()                        */
8730
/************************************************************************/
8731
8732
void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8733
                            sqlite3_value **argv);
8734
8735
void OGRGeoPackageTransform(sqlite3_context *pContext, int argc,
8736
                            sqlite3_value **argv)
8737
0
{
8738
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB ||
8739
0
        sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8740
0
    {
8741
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8742
0
        return;
8743
0
    }
8744
8745
0
    const int nBLOBLen = sqlite3_value_bytes(argv[0]);
8746
0
    const GByte *pabyBLOB =
8747
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
8748
0
    GPkgHeader sHeader;
8749
0
    if (!OGRGeoPackageGetHeader(pContext, argc, argv, &sHeader, false, false))
8750
0
    {
8751
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8752
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8753
0
        return;
8754
0
    }
8755
8756
0
    const int nDestSRID = sqlite3_value_int(argv[1]);
8757
0
    if (sHeader.iSrsId == nDestSRID)
8758
0
    {
8759
        // Return blob unmodified
8760
0
        sqlite3_result_blob(pContext, pabyBLOB, nBLOBLen, SQLITE_TRANSIENT);
8761
0
        return;
8762
0
    }
8763
8764
0
    GDALGeoPackageDataset *poDS =
8765
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8766
8767
    // Try to get the cached coordinate transformation
8768
0
    OGRCoordinateTransformation *poCT;
8769
0
    if (poDS->m_nLastCachedCTSrcSRId == sHeader.iSrsId &&
8770
0
        poDS->m_nLastCachedCTDstSRId == nDestSRID)
8771
0
    {
8772
0
        poCT = poDS->m_poLastCachedCT.get();
8773
0
    }
8774
0
    else
8775
0
    {
8776
0
        std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8777
0
            poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true));
8778
0
        if (poSrcSRS == nullptr)
8779
0
        {
8780
0
            CPLError(CE_Failure, CPLE_AppDefined,
8781
0
                     "SRID set on geometry (%d) is invalid", sHeader.iSrsId);
8782
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8783
0
            return;
8784
0
        }
8785
8786
0
        std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>
8787
0
            poDstSRS(poDS->GetSpatialRef(nDestSRID, true));
8788
0
        if (poDstSRS == nullptr)
8789
0
        {
8790
0
            CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid",
8791
0
                     nDestSRID);
8792
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8793
0
            return;
8794
0
        }
8795
0
        poCT =
8796
0
            OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get());
8797
0
        if (poCT == nullptr)
8798
0
        {
8799
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8800
0
            return;
8801
0
        }
8802
8803
        // Cache coordinate transformation for potential later reuse
8804
0
        poDS->m_nLastCachedCTSrcSRId = sHeader.iSrsId;
8805
0
        poDS->m_nLastCachedCTDstSRId = nDestSRID;
8806
0
        poDS->m_poLastCachedCT.reset(poCT);
8807
0
        poCT = poDS->m_poLastCachedCT.get();
8808
0
    }
8809
8810
0
    if (sHeader.nHeaderLen >= 8)
8811
0
    {
8812
0
        std::vector<GByte> &abyNewBLOB = poDS->m_abyWKBTransformCache;
8813
0
        abyNewBLOB.resize(nBLOBLen);
8814
0
        memcpy(abyNewBLOB.data(), pabyBLOB, nBLOBLen);
8815
8816
0
        OGREnvelope3D oEnv3d;
8817
0
        if (!OGRWKBTransform(abyNewBLOB.data() + sHeader.nHeaderLen,
8818
0
                             nBLOBLen - sHeader.nHeaderLen, poCT,
8819
0
                             poDS->m_oWKBTransformCache, oEnv3d) ||
8820
0
            !GPkgUpdateHeader(abyNewBLOB.data(), nBLOBLen, nDestSRID,
8821
0
                              oEnv3d.MinX, oEnv3d.MaxX, oEnv3d.MinY,
8822
0
                              oEnv3d.MaxY, oEnv3d.MinZ, oEnv3d.MaxZ))
8823
0
        {
8824
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8825
0
            sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8826
0
            return;
8827
0
        }
8828
8829
0
        sqlite3_result_blob(pContext, abyNewBLOB.data(), nBLOBLen,
8830
0
                            SQLITE_TRANSIENT);
8831
0
        return;
8832
0
    }
8833
8834
    // Try also spatialite geometry blobs
8835
0
    OGRGeometry *poGeomSpatialite = nullptr;
8836
0
    if (OGRSQLiteImportSpatiaLiteGeometry(pabyBLOB, nBLOBLen,
8837
0
                                          &poGeomSpatialite) != OGRERR_NONE)
8838
0
    {
8839
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid geometry");
8840
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8841
0
        return;
8842
0
    }
8843
0
    auto poGeom = std::unique_ptr<OGRGeometry>(poGeomSpatialite);
8844
8845
0
    if (poGeom->transform(poCT) != OGRERR_NONE)
8846
0
    {
8847
0
        sqlite3_result_blob(pContext, nullptr, 0, nullptr);
8848
0
        return;
8849
0
    }
8850
8851
0
    size_t nBLOBDestLen = 0;
8852
0
    GByte *pabyDestBLOB =
8853
0
        GPkgGeometryFromOGR(poGeom.get(), nDestSRID, nullptr, &nBLOBDestLen);
8854
0
    if (!pabyDestBLOB)
8855
0
    {
8856
0
        sqlite3_result_null(pContext);
8857
0
        return;
8858
0
    }
8859
0
    sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
8860
0
                        VSIFree);
8861
0
}
8862
8863
/************************************************************************/
8864
/*                      OGRGeoPackageSridFromAuthCRS()                  */
8865
/************************************************************************/
8866
8867
static void OGRGeoPackageSridFromAuthCRS(sqlite3_context *pContext,
8868
                                         int /*argc*/, sqlite3_value **argv)
8869
0
{
8870
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8871
0
        sqlite3_value_type(argv[1]) != SQLITE_INTEGER)
8872
0
    {
8873
0
        sqlite3_result_int(pContext, -1);
8874
0
        return;
8875
0
    }
8876
8877
0
    GDALGeoPackageDataset *poDS =
8878
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8879
8880
0
    char *pszSQL = sqlite3_mprintf(
8881
0
        "SELECT srs_id FROM gpkg_spatial_ref_sys WHERE "
8882
0
        "lower(organization) = lower('%q') AND organization_coordsys_id = %d",
8883
0
        sqlite3_value_text(argv[0]), sqlite3_value_int(argv[1]));
8884
0
    OGRErr err = OGRERR_NONE;
8885
0
    int nSRSId = SQLGetInteger(poDS->GetDB(), pszSQL, &err);
8886
0
    sqlite3_free(pszSQL);
8887
0
    if (err != OGRERR_NONE)
8888
0
        nSRSId = -1;
8889
0
    sqlite3_result_int(pContext, nSRSId);
8890
0
}
8891
8892
/************************************************************************/
8893
/*                    OGRGeoPackageImportFromEPSG()                     */
8894
/************************************************************************/
8895
8896
static void OGRGeoPackageImportFromEPSG(sqlite3_context *pContext, int /*argc*/,
8897
                                        sqlite3_value **argv)
8898
0
{
8899
0
    if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER)
8900
0
    {
8901
0
        sqlite3_result_int(pContext, -1);
8902
0
        return;
8903
0
    }
8904
8905
0
    GDALGeoPackageDataset *poDS =
8906
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8907
0
    OGRSpatialReference oSRS;
8908
0
    if (oSRS.importFromEPSG(sqlite3_value_int(argv[0])) != OGRERR_NONE)
8909
0
    {
8910
0
        sqlite3_result_int(pContext, -1);
8911
0
        return;
8912
0
    }
8913
8914
0
    sqlite3_result_int(pContext, poDS->GetSrsId(&oSRS));
8915
0
}
8916
8917
/************************************************************************/
8918
/*               OGRGeoPackageRegisterGeometryExtension()               */
8919
/************************************************************************/
8920
8921
static void OGRGeoPackageRegisterGeometryExtension(sqlite3_context *pContext,
8922
                                                   int /*argc*/,
8923
                                                   sqlite3_value **argv)
8924
0
{
8925
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8926
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT ||
8927
0
        sqlite3_value_type(argv[2]) != SQLITE_TEXT)
8928
0
    {
8929
0
        sqlite3_result_int(pContext, 0);
8930
0
        return;
8931
0
    }
8932
8933
0
    const char *pszTableName =
8934
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8935
0
    const char *pszGeomName =
8936
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8937
0
    const char *pszGeomType =
8938
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
8939
8940
0
    GDALGeoPackageDataset *poDS =
8941
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8942
8943
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8944
0
        poDS->GetLayerByName(pszTableName));
8945
0
    if (poLyr == nullptr)
8946
0
    {
8947
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8948
0
        sqlite3_result_int(pContext, 0);
8949
0
        return;
8950
0
    }
8951
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
8952
0
    {
8953
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
8954
0
        sqlite3_result_int(pContext, 0);
8955
0
        return;
8956
0
    }
8957
0
    const OGRwkbGeometryType eGeomType = OGRFromOGCGeomType(pszGeomType);
8958
0
    if (eGeomType == wkbUnknown)
8959
0
    {
8960
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry type name");
8961
0
        sqlite3_result_int(pContext, 0);
8962
0
        return;
8963
0
    }
8964
8965
0
    sqlite3_result_int(
8966
0
        pContext,
8967
0
        static_cast<int>(poLyr->CreateGeometryExtensionIfNecessary(eGeomType)));
8968
0
}
8969
8970
/************************************************************************/
8971
/*                  OGRGeoPackageCreateSpatialIndex()                   */
8972
/************************************************************************/
8973
8974
static void OGRGeoPackageCreateSpatialIndex(sqlite3_context *pContext,
8975
                                            int /*argc*/, sqlite3_value **argv)
8976
0
{
8977
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
8978
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
8979
0
    {
8980
0
        sqlite3_result_int(pContext, 0);
8981
0
        return;
8982
0
    }
8983
8984
0
    const char *pszTableName =
8985
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
8986
0
    const char *pszGeomName =
8987
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
8988
0
    GDALGeoPackageDataset *poDS =
8989
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
8990
8991
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
8992
0
        poDS->GetLayerByName(pszTableName));
8993
0
    if (poLyr == nullptr)
8994
0
    {
8995
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
8996
0
        sqlite3_result_int(pContext, 0);
8997
0
        return;
8998
0
    }
8999
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9000
0
    {
9001
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9002
0
        sqlite3_result_int(pContext, 0);
9003
0
        return;
9004
0
    }
9005
9006
0
    sqlite3_result_int(pContext, poLyr->CreateSpatialIndex());
9007
0
}
9008
9009
/************************************************************************/
9010
/*                  OGRGeoPackageDisableSpatialIndex()                  */
9011
/************************************************************************/
9012
9013
static void OGRGeoPackageDisableSpatialIndex(sqlite3_context *pContext,
9014
                                             int /*argc*/, sqlite3_value **argv)
9015
0
{
9016
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9017
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9018
0
    {
9019
0
        sqlite3_result_int(pContext, 0);
9020
0
        return;
9021
0
    }
9022
9023
0
    const char *pszTableName =
9024
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9025
0
    const char *pszGeomName =
9026
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9027
0
    GDALGeoPackageDataset *poDS =
9028
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9029
9030
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9031
0
        poDS->GetLayerByName(pszTableName));
9032
0
    if (poLyr == nullptr)
9033
0
    {
9034
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9035
0
        sqlite3_result_int(pContext, 0);
9036
0
        return;
9037
0
    }
9038
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9039
0
    {
9040
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9041
0
        sqlite3_result_int(pContext, 0);
9042
0
        return;
9043
0
    }
9044
9045
0
    sqlite3_result_int(pContext, poLyr->DropSpatialIndex(true));
9046
0
}
9047
9048
/************************************************************************/
9049
/*                  OGRGeoPackageHasSpatialIndex()                      */
9050
/************************************************************************/
9051
9052
static void OGRGeoPackageHasSpatialIndex(sqlite3_context *pContext,
9053
                                         int /*argc*/, sqlite3_value **argv)
9054
0
{
9055
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9056
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9057
0
    {
9058
0
        sqlite3_result_int(pContext, 0);
9059
0
        return;
9060
0
    }
9061
9062
0
    const char *pszTableName =
9063
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9064
0
    const char *pszGeomName =
9065
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9066
0
    GDALGeoPackageDataset *poDS =
9067
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9068
9069
0
    OGRGeoPackageTableLayer *poLyr = cpl::down_cast<OGRGeoPackageTableLayer *>(
9070
0
        poDS->GetLayerByName(pszTableName));
9071
0
    if (poLyr == nullptr)
9072
0
    {
9073
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer name");
9074
0
        sqlite3_result_int(pContext, 0);
9075
0
        return;
9076
0
    }
9077
0
    if (!EQUAL(poLyr->GetGeometryColumn(), pszGeomName))
9078
0
    {
9079
0
        CPLError(CE_Failure, CPLE_AppDefined, "Unknown geometry column name");
9080
0
        sqlite3_result_int(pContext, 0);
9081
0
        return;
9082
0
    }
9083
9084
0
    poLyr->RunDeferredCreationIfNecessary();
9085
0
    poLyr->CreateSpatialIndexIfNecessary();
9086
9087
0
    sqlite3_result_int(pContext, poLyr->HasSpatialIndex());
9088
0
}
9089
9090
/************************************************************************/
9091
/*                       GPKG_hstore_get_value()                        */
9092
/************************************************************************/
9093
9094
static void GPKG_hstore_get_value(sqlite3_context *pContext, int /*argc*/,
9095
                                  sqlite3_value **argv)
9096
0
{
9097
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT ||
9098
0
        sqlite3_value_type(argv[1]) != SQLITE_TEXT)
9099
0
    {
9100
0
        sqlite3_result_null(pContext);
9101
0
        return;
9102
0
    }
9103
9104
0
    const char *pszHStore =
9105
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9106
0
    const char *pszSearchedKey =
9107
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[1]));
9108
0
    char *pszValue = OGRHStoreGetValue(pszHStore, pszSearchedKey);
9109
0
    if (pszValue != nullptr)
9110
0
        sqlite3_result_text(pContext, pszValue, -1, CPLFree);
9111
0
    else
9112
0
        sqlite3_result_null(pContext);
9113
0
}
9114
9115
/************************************************************************/
9116
/*                      GPKG_GDAL_GetMemFileFromBlob()                  */
9117
/************************************************************************/
9118
9119
static CPLString GPKG_GDAL_GetMemFileFromBlob(sqlite3_value **argv)
9120
0
{
9121
0
    int nBytes = sqlite3_value_bytes(argv[0]);
9122
0
    const GByte *pabyBLOB =
9123
0
        reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[0]));
9124
0
    CPLString osMemFileName(
9125
0
        VSIMemGenerateHiddenFilename("GPKG_GDAL_GetMemFileFromBlob"));
9126
0
    VSILFILE *fp = VSIFileFromMemBuffer(
9127
0
        osMemFileName.c_str(), const_cast<GByte *>(pabyBLOB), nBytes, FALSE);
9128
0
    VSIFCloseL(fp);
9129
0
    return osMemFileName;
9130
0
}
9131
9132
/************************************************************************/
9133
/*                       GPKG_GDAL_GetMimeType()                        */
9134
/************************************************************************/
9135
9136
static void GPKG_GDAL_GetMimeType(sqlite3_context *pContext, int /*argc*/,
9137
                                  sqlite3_value **argv)
9138
0
{
9139
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9140
0
    {
9141
0
        sqlite3_result_null(pContext);
9142
0
        return;
9143
0
    }
9144
9145
0
    CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9146
0
    GDALDriver *poDriver =
9147
0
        GDALDriver::FromHandle(GDALIdentifyDriver(osMemFileName, nullptr));
9148
0
    if (poDriver != nullptr)
9149
0
    {
9150
0
        const char *pszRes = nullptr;
9151
0
        if (EQUAL(poDriver->GetDescription(), "PNG"))
9152
0
            pszRes = "image/png";
9153
0
        else if (EQUAL(poDriver->GetDescription(), "JPEG"))
9154
0
            pszRes = "image/jpeg";
9155
0
        else if (EQUAL(poDriver->GetDescription(), "WEBP"))
9156
0
            pszRes = "image/x-webp";
9157
0
        else if (EQUAL(poDriver->GetDescription(), "GTIFF"))
9158
0
            pszRes = "image/tiff";
9159
0
        else
9160
0
            pszRes = CPLSPrintf("gdal/%s", poDriver->GetDescription());
9161
0
        sqlite3_result_text(pContext, pszRes, -1, SQLITE_TRANSIENT);
9162
0
    }
9163
0
    else
9164
0
        sqlite3_result_null(pContext);
9165
0
    VSIUnlink(osMemFileName);
9166
0
}
9167
9168
/************************************************************************/
9169
/*                       GPKG_GDAL_GetBandCount()                       */
9170
/************************************************************************/
9171
9172
static void GPKG_GDAL_GetBandCount(sqlite3_context *pContext, int /*argc*/,
9173
                                   sqlite3_value **argv)
9174
0
{
9175
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9176
0
    {
9177
0
        sqlite3_result_null(pContext);
9178
0
        return;
9179
0
    }
9180
9181
0
    CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9182
0
    auto poDS = std::unique_ptr<GDALDataset>(
9183
0
        GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9184
0
                          nullptr, nullptr, nullptr));
9185
0
    if (poDS != nullptr)
9186
0
    {
9187
0
        sqlite3_result_int(pContext, poDS->GetRasterCount());
9188
0
    }
9189
0
    else
9190
0
        sqlite3_result_null(pContext);
9191
0
    VSIUnlink(osMemFileName);
9192
0
}
9193
9194
/************************************************************************/
9195
/*                       GPKG_GDAL_HasColorTable()                      */
9196
/************************************************************************/
9197
9198
static void GPKG_GDAL_HasColorTable(sqlite3_context *pContext, int /*argc*/,
9199
                                    sqlite3_value **argv)
9200
0
{
9201
0
    if (sqlite3_value_type(argv[0]) != SQLITE_BLOB)
9202
0
    {
9203
0
        sqlite3_result_null(pContext);
9204
0
        return;
9205
0
    }
9206
9207
0
    CPLString osMemFileName(GPKG_GDAL_GetMemFileFromBlob(argv));
9208
0
    auto poDS = std::unique_ptr<GDALDataset>(
9209
0
        GDALDataset::Open(osMemFileName, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
9210
0
                          nullptr, nullptr, nullptr));
9211
0
    if (poDS != nullptr)
9212
0
    {
9213
0
        sqlite3_result_int(
9214
0
            pContext, poDS->GetRasterCount() == 1 &&
9215
0
                          poDS->GetRasterBand(1)->GetColorTable() != nullptr);
9216
0
    }
9217
0
    else
9218
0
        sqlite3_result_null(pContext);
9219
0
    VSIUnlink(osMemFileName);
9220
0
}
9221
9222
/************************************************************************/
9223
/*                      GetRasterLayerDataset()                         */
9224
/************************************************************************/
9225
9226
GDALDataset *
9227
GDALGeoPackageDataset::GetRasterLayerDataset(const char *pszLayerName)
9228
0
{
9229
0
    const auto oIter = m_oCachedRasterDS.find(pszLayerName);
9230
0
    if (oIter != m_oCachedRasterDS.end())
9231
0
        return oIter->second.get();
9232
9233
0
    auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
9234
0
        (std::string("GPKG:\"") + m_pszFilename + "\":" + pszLayerName).c_str(),
9235
0
        GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
9236
0
    if (!poDS)
9237
0
    {
9238
0
        return nullptr;
9239
0
    }
9240
0
    m_oCachedRasterDS[pszLayerName] = std::move(poDS);
9241
0
    return m_oCachedRasterDS[pszLayerName].get();
9242
0
}
9243
9244
/************************************************************************/
9245
/*                   GPKG_gdal_get_layer_pixel_value()                  */
9246
/************************************************************************/
9247
9248
// NOTE: keep in sync implementations in ogrsqlitesqlfunctionscommon.cpp
9249
// and ogrgeopackagedatasource.cpp
9250
static void GPKG_gdal_get_layer_pixel_value(sqlite3_context *pContext, int argc,
9251
                                            sqlite3_value **argv)
9252
0
{
9253
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9254
0
    {
9255
0
        CPLError(CE_Failure, CPLE_AppDefined,
9256
0
                 "Invalid arguments to gdal_get_layer_pixel_value()");
9257
0
        sqlite3_result_null(pContext);
9258
0
        return;
9259
0
    }
9260
9261
0
    const char *pszLayerName =
9262
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9263
9264
0
    GDALGeoPackageDataset *poGlobalDS =
9265
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9266
0
    auto poDS = poGlobalDS->GetRasterLayerDataset(pszLayerName);
9267
0
    if (!poDS)
9268
0
    {
9269
0
        sqlite3_result_null(pContext);
9270
0
        return;
9271
0
    }
9272
9273
0
    OGRSQLite_gdal_get_pixel_value_common("gdal_get_layer_pixel_value",
9274
0
                                          pContext, argc, argv, poDS);
9275
0
}
9276
9277
/************************************************************************/
9278
/*                       GPKG_ogr_layer_Extent()                        */
9279
/************************************************************************/
9280
9281
static void GPKG_ogr_layer_Extent(sqlite3_context *pContext, int /*argc*/,
9282
                                  sqlite3_value **argv)
9283
0
{
9284
0
    if (sqlite3_value_type(argv[0]) != SQLITE_TEXT)
9285
0
    {
9286
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: Invalid argument type",
9287
0
                 "ogr_layer_Extent");
9288
0
        sqlite3_result_null(pContext);
9289
0
        return;
9290
0
    }
9291
9292
0
    const char *pszLayerName =
9293
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[0]));
9294
0
    GDALGeoPackageDataset *poDS =
9295
0
        static_cast<GDALGeoPackageDataset *>(sqlite3_user_data(pContext));
9296
0
    OGRLayer *poLayer = poDS->GetLayerByName(pszLayerName);
9297
0
    if (!poLayer)
9298
0
    {
9299
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: unknown layer",
9300
0
                 "ogr_layer_Extent");
9301
0
        sqlite3_result_null(pContext);
9302
0
        return;
9303
0
    }
9304
9305
0
    if (poLayer->GetGeomType() == wkbNone)
9306
0
    {
9307
0
        sqlite3_result_null(pContext);
9308
0
        return;
9309
0
    }
9310
9311
0
    OGREnvelope sExtent;
9312
0
    if (poLayer->GetExtent(&sExtent) != OGRERR_NONE)
9313
0
    {
9314
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s: Cannot fetch layer extent",
9315
0
                 "ogr_layer_Extent");
9316
0
        sqlite3_result_null(pContext);
9317
0
        return;
9318
0
    }
9319
9320
0
    OGRPolygon oPoly;
9321
0
    auto poRing = std::make_unique<OGRLinearRing>();
9322
0
    poRing->addPoint(sExtent.MinX, sExtent.MinY);
9323
0
    poRing->addPoint(sExtent.MaxX, sExtent.MinY);
9324
0
    poRing->addPoint(sExtent.MaxX, sExtent.MaxY);
9325
0
    poRing->addPoint(sExtent.MinX, sExtent.MaxY);
9326
0
    poRing->addPoint(sExtent.MinX, sExtent.MinY);
9327
0
    oPoly.addRing(std::move(poRing));
9328
9329
0
    const auto poSRS = poLayer->GetSpatialRef();
9330
0
    const int nSRID = poDS->GetSrsId(poSRS);
9331
0
    size_t nBLOBDestLen = 0;
9332
0
    GByte *pabyDestBLOB =
9333
0
        GPkgGeometryFromOGR(&oPoly, nSRID, nullptr, &nBLOBDestLen);
9334
0
    if (!pabyDestBLOB)
9335
0
    {
9336
0
        sqlite3_result_null(pContext);
9337
0
        return;
9338
0
    }
9339
0
    sqlite3_result_blob(pContext, pabyDestBLOB, static_cast<int>(nBLOBDestLen),
9340
0
                        VSIFree);
9341
0
}
9342
9343
/************************************************************************/
9344
/*                      InstallSQLFunctions()                           */
9345
/************************************************************************/
9346
9347
#ifndef SQLITE_DETERMINISTIC
9348
#define SQLITE_DETERMINISTIC 0
9349
#endif
9350
9351
#ifndef SQLITE_INNOCUOUS
9352
#define SQLITE_INNOCUOUS 0
9353
#endif
9354
9355
#ifndef UTF8_INNOCUOUS
9356
#define UTF8_INNOCUOUS (SQLITE_UTF8 | SQLITE_DETERMINISTIC | SQLITE_INNOCUOUS)
9357
#endif
9358
9359
void GDALGeoPackageDataset::InstallSQLFunctions()
9360
2.38k
{
9361
2.38k
    InitSpatialite();
9362
9363
    // Enable SpatiaLite 4.3 GPKG mode, i.e. that SpatiaLite functions
9364
    // that take geometries will accept and return GPKG encoded geometries without
9365
    // explicit conversion.
9366
    // Use sqlite3_exec() instead of SQLCommand() since we don't want verbose
9367
    // error.
9368
2.38k
    sqlite3_exec(hDB, "SELECT EnableGpkgMode()", nullptr, nullptr, nullptr);
9369
9370
    /* Used by RTree Spatial Index Extension */
9371
2.38k
    sqlite3_create_function(hDB, "ST_MinX", 1, UTF8_INNOCUOUS, nullptr,
9372
2.38k
                            OGRGeoPackageSTMinX, nullptr, nullptr);
9373
2.38k
    sqlite3_create_function(hDB, "ST_MinY", 1, UTF8_INNOCUOUS, nullptr,
9374
2.38k
                            OGRGeoPackageSTMinY, nullptr, nullptr);
9375
2.38k
    sqlite3_create_function(hDB, "ST_MaxX", 1, UTF8_INNOCUOUS, nullptr,
9376
2.38k
                            OGRGeoPackageSTMaxX, nullptr, nullptr);
9377
2.38k
    sqlite3_create_function(hDB, "ST_MaxY", 1, UTF8_INNOCUOUS, nullptr,
9378
2.38k
                            OGRGeoPackageSTMaxY, nullptr, nullptr);
9379
2.38k
    sqlite3_create_function(hDB, "ST_IsEmpty", 1, UTF8_INNOCUOUS, nullptr,
9380
2.38k
                            OGRGeoPackageSTIsEmpty, nullptr, nullptr);
9381
9382
    /* Used by Geometry Type Triggers Extension */
9383
2.38k
    sqlite3_create_function(hDB, "ST_GeometryType", 1, UTF8_INNOCUOUS, nullptr,
9384
2.38k
                            OGRGeoPackageSTGeometryType, nullptr, nullptr);
9385
2.38k
    sqlite3_create_function(hDB, "GPKG_IsAssignable", 2, UTF8_INNOCUOUS,
9386
2.38k
                            nullptr, OGRGeoPackageGPKGIsAssignable, nullptr,
9387
2.38k
                            nullptr);
9388
9389
    /* Used by Geometry SRS ID Triggers Extension */
9390
2.38k
    sqlite3_create_function(hDB, "ST_SRID", 1, UTF8_INNOCUOUS, nullptr,
9391
2.38k
                            OGRGeoPackageSTSRID, nullptr, nullptr);
9392
9393
    /* Spatialite-like functions */
9394
2.38k
    sqlite3_create_function(hDB, "CreateSpatialIndex", 2, SQLITE_UTF8, this,
9395
2.38k
                            OGRGeoPackageCreateSpatialIndex, nullptr, nullptr);
9396
2.38k
    sqlite3_create_function(hDB, "DisableSpatialIndex", 2, SQLITE_UTF8, this,
9397
2.38k
                            OGRGeoPackageDisableSpatialIndex, nullptr, nullptr);
9398
2.38k
    sqlite3_create_function(hDB, "HasSpatialIndex", 2, SQLITE_UTF8, this,
9399
2.38k
                            OGRGeoPackageHasSpatialIndex, nullptr, nullptr);
9400
9401
    // HSTORE functions
9402
2.38k
    sqlite3_create_function(hDB, "hstore_get_value", 2, UTF8_INNOCUOUS, nullptr,
9403
2.38k
                            GPKG_hstore_get_value, nullptr, nullptr);
9404
9405
    // Override a few Spatialite functions to work with gpkg_spatial_ref_sys
9406
2.38k
    sqlite3_create_function(hDB, "ST_Transform", 2, UTF8_INNOCUOUS, this,
9407
2.38k
                            OGRGeoPackageTransform, nullptr, nullptr);
9408
2.38k
    sqlite3_create_function(hDB, "Transform", 2, UTF8_INNOCUOUS, this,
9409
2.38k
                            OGRGeoPackageTransform, nullptr, nullptr);
9410
2.38k
    sqlite3_create_function(hDB, "SridFromAuthCRS", 2, SQLITE_UTF8, this,
9411
2.38k
                            OGRGeoPackageSridFromAuthCRS, nullptr, nullptr);
9412
9413
2.38k
    sqlite3_create_function(hDB, "ST_EnvIntersects", 2, UTF8_INNOCUOUS, nullptr,
9414
2.38k
                            OGRGeoPackageSTEnvelopesIntersectsTwoParams,
9415
2.38k
                            nullptr, nullptr);
9416
2.38k
    sqlite3_create_function(
9417
2.38k
        hDB, "ST_EnvelopesIntersects", 2, UTF8_INNOCUOUS, nullptr,
9418
2.38k
        OGRGeoPackageSTEnvelopesIntersectsTwoParams, nullptr, nullptr);
9419
9420
2.38k
    sqlite3_create_function(hDB, "ST_EnvIntersects", 5, UTF8_INNOCUOUS, nullptr,
9421
2.38k
                            OGRGeoPackageSTEnvelopesIntersects, nullptr,
9422
2.38k
                            nullptr);
9423
2.38k
    sqlite3_create_function(hDB, "ST_EnvelopesIntersects", 5, UTF8_INNOCUOUS,
9424
2.38k
                            nullptr, OGRGeoPackageSTEnvelopesIntersects,
9425
2.38k
                            nullptr, nullptr);
9426
9427
    // Implementation that directly hacks the GeoPackage geometry blob header
9428
2.38k
    sqlite3_create_function(hDB, "SetSRID", 2, UTF8_INNOCUOUS, nullptr,
9429
2.38k
                            OGRGeoPackageSetSRID, nullptr, nullptr);
9430
9431
    // GDAL specific function
9432
2.38k
    sqlite3_create_function(hDB, "ImportFromEPSG", 1, SQLITE_UTF8, this,
9433
2.38k
                            OGRGeoPackageImportFromEPSG, nullptr, nullptr);
9434
9435
    // May be used by ogrmerge.py
9436
2.38k
    sqlite3_create_function(hDB, "RegisterGeometryExtension", 3, SQLITE_UTF8,
9437
2.38k
                            this, OGRGeoPackageRegisterGeometryExtension,
9438
2.38k
                            nullptr, nullptr);
9439
9440
2.38k
    if (OGRGeometryFactory::haveGEOS())
9441
0
    {
9442
0
        sqlite3_create_function(hDB, "ST_MakeValid", 1, UTF8_INNOCUOUS, nullptr,
9443
0
                                OGRGeoPackageSTMakeValid, nullptr, nullptr);
9444
0
    }
9445
9446
2.38k
    sqlite3_create_function(hDB, "ST_Length", 1, UTF8_INNOCUOUS, nullptr,
9447
2.38k
                            OGRGeoPackageLengthOrGeodesicLength, nullptr,
9448
2.38k
                            nullptr);
9449
2.38k
    sqlite3_create_function(hDB, "ST_Length", 2, UTF8_INNOCUOUS, this,
9450
2.38k
                            OGRGeoPackageLengthOrGeodesicLength, nullptr,
9451
2.38k
                            nullptr);
9452
9453
2.38k
    sqlite3_create_function(hDB, "ST_Area", 1, UTF8_INNOCUOUS, nullptr,
9454
2.38k
                            OGRGeoPackageSTArea, nullptr, nullptr);
9455
2.38k
    sqlite3_create_function(hDB, "ST_Area", 2, UTF8_INNOCUOUS, this,
9456
2.38k
                            OGRGeoPackageGeodesicArea, nullptr, nullptr);
9457
9458
    // Debug functions
9459
2.38k
    if (CPLTestBool(CPLGetConfigOption("GPKG_DEBUG", "FALSE")))
9460
0
    {
9461
0
        sqlite3_create_function(hDB, "GDAL_GetMimeType", 1,
9462
0
                                SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9463
0
                                GPKG_GDAL_GetMimeType, nullptr, nullptr);
9464
0
        sqlite3_create_function(hDB, "GDAL_GetBandCount", 1,
9465
0
                                SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9466
0
                                GPKG_GDAL_GetBandCount, nullptr, nullptr);
9467
0
        sqlite3_create_function(hDB, "GDAL_HasColorTable", 1,
9468
0
                                SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
9469
0
                                GPKG_GDAL_HasColorTable, nullptr, nullptr);
9470
0
    }
9471
9472
2.38k
    sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 5, SQLITE_UTF8,
9473
2.38k
                            this, GPKG_gdal_get_layer_pixel_value, nullptr,
9474
2.38k
                            nullptr);
9475
2.38k
    sqlite3_create_function(hDB, "gdal_get_layer_pixel_value", 6, SQLITE_UTF8,
9476
2.38k
                            this, GPKG_gdal_get_layer_pixel_value, nullptr,
9477
2.38k
                            nullptr);
9478
9479
    // Function from VirtualOGR
9480
2.38k
    sqlite3_create_function(hDB, "ogr_layer_Extent", 1, SQLITE_UTF8, this,
9481
2.38k
                            GPKG_ogr_layer_Extent, nullptr, nullptr);
9482
9483
2.38k
    m_pSQLFunctionData = OGRSQLiteRegisterSQLFunctionsCommon(hDB);
9484
2.38k
}
9485
9486
/************************************************************************/
9487
/*                         OpenOrCreateDB()                             */
9488
/************************************************************************/
9489
9490
bool GDALGeoPackageDataset::OpenOrCreateDB(int flags)
9491
1.12k
{
9492
1.12k
    const bool bSuccess = OGRSQLiteBaseDataSource::OpenOrCreateDB(
9493
1.12k
        flags, /*bRegisterOGR2SQLiteExtensions=*/false,
9494
1.12k
        /*bLoadExtensions=*/true);
9495
1.12k
    if (!bSuccess)
9496
204
        return false;
9497
9498
    // Turning on recursive_triggers is needed so that DELETE triggers fire
9499
    // in a INSERT OR REPLACE statement. In particular this is needed to
9500
    // make sure gpkg_ogr_contents.feature_count is properly updated.
9501
917
    SQLCommand(hDB, "PRAGMA recursive_triggers = 1");
9502
9503
917
    InstallSQLFunctions();
9504
9505
917
    const char *pszSqlitePragma =
9506
917
        CPLGetConfigOption("OGR_SQLITE_PRAGMA", nullptr);
9507
917
    OGRErr eErr = OGRERR_NONE;
9508
917
    if ((!pszSqlitePragma || !strstr(pszSqlitePragma, "trusted_schema")) &&
9509
        // Older sqlite versions don't have this pragma
9510
917
        SQLGetInteger(hDB, "PRAGMA trusted_schema", &eErr) == 0 &&
9511
0
        eErr == OGRERR_NONE)
9512
0
    {
9513
0
        bool bNeedsTrustedSchema = false;
9514
9515
        // Current SQLite versions require PRAGMA trusted_schema = 1 to be
9516
        // able to use the RTree from triggers, which is only needed when
9517
        // modifying the RTree.
9518
0
        if (((flags & SQLITE_OPEN_READWRITE) != 0 ||
9519
0
             (flags & SQLITE_OPEN_CREATE) != 0) &&
9520
0
            OGRSQLiteRTreeRequiresTrustedSchemaOn())
9521
0
        {
9522
0
            bNeedsTrustedSchema = true;
9523
0
        }
9524
9525
#ifdef HAVE_SPATIALITE
9526
        // Spatialite <= 5.1.0 doesn't declare its functions as SQLITE_INNOCUOUS
9527
        if (!bNeedsTrustedSchema && HasExtensionsTable() &&
9528
            SQLGetInteger(
9529
                hDB,
9530
                "SELECT 1 FROM gpkg_extensions WHERE "
9531
                "extension_name ='gdal_spatialite_computed_geom_column'",
9532
                nullptr) == 1 &&
9533
            SpatialiteRequiresTrustedSchemaOn() && AreSpatialiteTriggersSafe())
9534
        {
9535
            bNeedsTrustedSchema = true;
9536
        }
9537
#endif
9538
9539
0
        if (bNeedsTrustedSchema)
9540
0
        {
9541
0
            CPLDebug("GPKG", "Setting PRAGMA trusted_schema = 1");
9542
0
            SQLCommand(hDB, "PRAGMA trusted_schema = 1");
9543
0
        }
9544
0
    }
9545
9546
917
    const char *pszPreludeStatements =
9547
917
        CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
9548
917
    if (pszPreludeStatements)
9549
0
    {
9550
0
        if (SQLCommand(hDB, pszPreludeStatements) != OGRERR_NONE)
9551
0
            return false;
9552
0
    }
9553
9554
917
    return true;
9555
917
}
9556
9557
/************************************************************************/
9558
/*                   GetLayerWithGetSpatialWhereByName()                */
9559
/************************************************************************/
9560
9561
std::pair<OGRLayer *, IOGRSQLiteGetSpatialWhere *>
9562
GDALGeoPackageDataset::GetLayerWithGetSpatialWhereByName(const char *pszName)
9563
0
{
9564
0
    OGRGeoPackageLayer *poRet =
9565
0
        cpl::down_cast<OGRGeoPackageLayer *>(GetLayerByName(pszName));
9566
0
    return std::pair(poRet, poRet);
9567
0
}
9568
9569
/************************************************************************/
9570
/*                       CommitTransaction()                            */
9571
/************************************************************************/
9572
9573
OGRErr GDALGeoPackageDataset::CommitTransaction()
9574
9575
237k
{
9576
237k
    if (m_nSoftTransactionLevel == 1)
9577
237k
    {
9578
237k
        FlushMetadata();
9579
237k
        for (auto &poLayer : m_apoLayers)
9580
1.40M
        {
9581
1.40M
            poLayer->DoJobAtTransactionCommit();
9582
1.40M
        }
9583
237k
    }
9584
9585
237k
    return OGRSQLiteBaseDataSource::CommitTransaction();
9586
237k
}
9587
9588
/************************************************************************/
9589
/*                     RollbackTransaction()                            */
9590
/************************************************************************/
9591
9592
OGRErr GDALGeoPackageDataset::RollbackTransaction()
9593
9594
40
{
9595
40
#ifdef ENABLE_GPKG_OGR_CONTENTS
9596
40
    std::vector<bool> abAddTriggers;
9597
40
    std::vector<bool> abTriggersDeletedInTransaction;
9598
40
#endif
9599
40
    if (m_nSoftTransactionLevel == 1)
9600
40
    {
9601
40
        FlushMetadata();
9602
40
        for (auto &poLayer : m_apoLayers)
9603
331
        {
9604
331
#ifdef ENABLE_GPKG_OGR_CONTENTS
9605
331
            abAddTriggers.push_back(poLayer->GetAddOGRFeatureCountTriggers());
9606
331
            abTriggersDeletedInTransaction.push_back(
9607
331
                poLayer->GetOGRFeatureCountTriggersDeletedInTransaction());
9608
331
            poLayer->SetAddOGRFeatureCountTriggers(false);
9609
331
#endif
9610
331
            poLayer->DoJobAtTransactionRollback();
9611
331
#ifdef ENABLE_GPKG_OGR_CONTENTS
9612
331
            poLayer->DisableFeatureCount();
9613
331
#endif
9614
331
        }
9615
40
    }
9616
9617
40
    const OGRErr eErr = OGRSQLiteBaseDataSource::RollbackTransaction();
9618
9619
40
#ifdef ENABLE_GPKG_OGR_CONTENTS
9620
40
    if (!abAddTriggers.empty())
9621
38
    {
9622
369
        for (size_t i = 0; i < m_apoLayers.size(); ++i)
9623
331
        {
9624
331
            auto &poLayer = m_apoLayers[i];
9625
331
            if (abTriggersDeletedInTransaction[i])
9626
0
            {
9627
0
                poLayer->SetOGRFeatureCountTriggersEnabled(true);
9628
0
            }
9629
331
            else
9630
331
            {
9631
331
                poLayer->SetAddOGRFeatureCountTriggers(abAddTriggers[i]);
9632
331
            }
9633
331
        }
9634
38
    }
9635
40
#endif
9636
40
    return eErr;
9637
40
}
9638
9639
/************************************************************************/
9640
/*                       GetGeometryTypeString()                        */
9641
/************************************************************************/
9642
9643
const char *
9644
GDALGeoPackageDataset::GetGeometryTypeString(OGRwkbGeometryType eType)
9645
2.70k
{
9646
2.70k
    const char *pszGPKGGeomType = OGRToOGCGeomType(eType);
9647
2.70k
    if (EQUAL(pszGPKGGeomType, "GEOMETRYCOLLECTION") &&
9648
0
        CPLTestBool(CPLGetConfigOption("OGR_GPKG_GEOMCOLLECTION", "NO")))
9649
0
    {
9650
0
        pszGPKGGeomType = "GEOMCOLLECTION";
9651
0
    }
9652
2.70k
    return pszGPKGGeomType;
9653
2.70k
}
9654
9655
/************************************************************************/
9656
/*                           GetFieldDomainNames()                      */
9657
/************************************************************************/
9658
9659
std::vector<std::string>
9660
GDALGeoPackageDataset::GetFieldDomainNames(CSLConstList) const
9661
0
{
9662
0
    if (!HasDataColumnConstraintsTable())
9663
0
        return std::vector<std::string>();
9664
9665
0
    std::vector<std::string> oDomainNamesList;
9666
9667
0
    std::unique_ptr<SQLResult> oResultTable;
9668
0
    {
9669
0
        std::string osSQL =
9670
0
            "SELECT DISTINCT constraint_name "
9671
0
            "FROM gpkg_data_column_constraints "
9672
0
            "WHERE constraint_name NOT LIKE '_%_domain_description' "
9673
0
            "ORDER BY constraint_name "
9674
0
            "LIMIT 10000"  // to avoid denial of service
9675
0
            ;
9676
0
        oResultTable = SQLQuery(hDB, osSQL.c_str());
9677
0
        if (!oResultTable)
9678
0
            return oDomainNamesList;
9679
0
    }
9680
9681
0
    if (oResultTable->RowCount() == 10000)
9682
0
    {
9683
0
        CPLError(CE_Warning, CPLE_AppDefined,
9684
0
                 "Number of rows returned for field domain names has been "
9685
0
                 "truncated.");
9686
0
    }
9687
0
    else if (oResultTable->RowCount() > 0)
9688
0
    {
9689
0
        oDomainNamesList.reserve(oResultTable->RowCount());
9690
0
        for (int i = 0; i < oResultTable->RowCount(); i++)
9691
0
        {
9692
0
            const char *pszConstraintName = oResultTable->GetValue(0, i);
9693
0
            if (!pszConstraintName)
9694
0
                continue;
9695
9696
0
            oDomainNamesList.emplace_back(pszConstraintName);
9697
0
        }
9698
0
    }
9699
9700
0
    return oDomainNamesList;
9701
0
}
9702
9703
/************************************************************************/
9704
/*                           GetFieldDomain()                           */
9705
/************************************************************************/
9706
9707
const OGRFieldDomain *
9708
GDALGeoPackageDataset::GetFieldDomain(const std::string &name) const
9709
0
{
9710
0
    const auto baseRet = GDALDataset::GetFieldDomain(name);
9711
0
    if (baseRet)
9712
0
        return baseRet;
9713
9714
0
    if (!HasDataColumnConstraintsTable())
9715
0
        return nullptr;
9716
9717
0
    const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
9718
0
    const char *min_is_inclusive =
9719
0
        bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
9720
0
    const char *max_is_inclusive =
9721
0
        bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
9722
9723
0
    std::unique_ptr<SQLResult> oResultTable;
9724
    // Note: for coded domains, we use a little trick by using a dummy
9725
    // _{domainname}_domain_description enum that has a single entry whose
9726
    // description is the description of the main domain.
9727
0
    {
9728
0
        char *pszSQL = sqlite3_mprintf(
9729
0
            "SELECT constraint_type, value, min, %s, "
9730
0
            "max, %s, description, constraint_name "
9731
0
            "FROM gpkg_data_column_constraints "
9732
0
            "WHERE constraint_name IN ('%q', "
9733
0
            "'_%q_domain_description') "
9734
0
            "AND length(constraint_type) < 100 "  // to
9735
                                                  // avoid
9736
                                                  // denial
9737
                                                  // of
9738
                                                  // service
9739
0
            "AND (value IS NULL OR length(value) < "
9740
0
            "10000) "  // to avoid denial
9741
                       // of service
9742
0
            "AND (description IS NULL OR "
9743
0
            "length(description) < 10000) "  // to
9744
                                             // avoid
9745
                                             // denial
9746
                                             // of
9747
                                             // service
9748
0
            "ORDER BY value "
9749
0
            "LIMIT 10000",  // to avoid denial of
9750
                            // service
9751
0
            min_is_inclusive, max_is_inclusive, name.c_str(), name.c_str());
9752
0
        oResultTable = SQLQuery(hDB, pszSQL);
9753
0
        sqlite3_free(pszSQL);
9754
0
        if (!oResultTable)
9755
0
            return nullptr;
9756
0
    }
9757
0
    if (oResultTable->RowCount() == 0)
9758
0
    {
9759
0
        return nullptr;
9760
0
    }
9761
0
    if (oResultTable->RowCount() == 10000)
9762
0
    {
9763
0
        CPLError(CE_Warning, CPLE_AppDefined,
9764
0
                 "Number of rows returned for field domain %s has been "
9765
0
                 "truncated.",
9766
0
                 name.c_str());
9767
0
    }
9768
9769
    // Try to find the field domain data type from fields that implement it
9770
0
    int nFieldType = -1;
9771
0
    OGRFieldSubType eSubType = OFSTNone;
9772
0
    if (HasDataColumnsTable())
9773
0
    {
9774
0
        char *pszSQL = sqlite3_mprintf(
9775
0
            "SELECT table_name, column_name FROM gpkg_data_columns WHERE "
9776
0
            "constraint_name = '%q' LIMIT 10",
9777
0
            name.c_str());
9778
0
        auto oResultTable2 = SQLQuery(hDB, pszSQL);
9779
0
        sqlite3_free(pszSQL);
9780
0
        if (oResultTable2 && oResultTable2->RowCount() >= 1)
9781
0
        {
9782
0
            for (int iRecord = 0; iRecord < oResultTable2->RowCount();
9783
0
                 iRecord++)
9784
0
            {
9785
0
                const char *pszTableName = oResultTable2->GetValue(0, iRecord);
9786
0
                const char *pszColumnName = oResultTable2->GetValue(1, iRecord);
9787
0
                if (pszTableName == nullptr || pszColumnName == nullptr)
9788
0
                    continue;
9789
0
                OGRLayer *poLayer =
9790
0
                    const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
9791
0
                        pszTableName);
9792
0
                if (poLayer)
9793
0
                {
9794
0
                    const auto poFDefn = poLayer->GetLayerDefn();
9795
0
                    int nIdx = poFDefn->GetFieldIndex(pszColumnName);
9796
0
                    if (nIdx >= 0)
9797
0
                    {
9798
0
                        const auto poFieldDefn = poFDefn->GetFieldDefn(nIdx);
9799
0
                        const auto eType = poFieldDefn->GetType();
9800
0
                        if (nFieldType < 0)
9801
0
                        {
9802
0
                            nFieldType = eType;
9803
0
                            eSubType = poFieldDefn->GetSubType();
9804
0
                        }
9805
0
                        else if ((eType == OFTInteger64 || eType == OFTReal) &&
9806
0
                                 nFieldType == OFTInteger)
9807
0
                        {
9808
                            // ok
9809
0
                        }
9810
0
                        else if (eType == OFTInteger &&
9811
0
                                 (nFieldType == OFTInteger64 ||
9812
0
                                  nFieldType == OFTReal))
9813
0
                        {
9814
0
                            nFieldType = OFTInteger;
9815
0
                            eSubType = OFSTNone;
9816
0
                        }
9817
0
                        else if (nFieldType != eType)
9818
0
                        {
9819
0
                            nFieldType = -1;
9820
0
                            eSubType = OFSTNone;
9821
0
                            break;
9822
0
                        }
9823
0
                    }
9824
0
                }
9825
0
            }
9826
0
        }
9827
0
    }
9828
9829
0
    std::unique_ptr<OGRFieldDomain> poDomain;
9830
0
    std::vector<OGRCodedValue> asValues;
9831
0
    bool error = false;
9832
0
    CPLString osLastConstraintType;
9833
0
    int nFieldTypeFromEnumCode = -1;
9834
0
    std::string osConstraintDescription;
9835
0
    std::string osDescrConstraintName("_");
9836
0
    osDescrConstraintName += name;
9837
0
    osDescrConstraintName += "_domain_description";
9838
0
    for (int iRecord = 0; iRecord < oResultTable->RowCount(); iRecord++)
9839
0
    {
9840
0
        const char *pszConstraintType = oResultTable->GetValue(0, iRecord);
9841
0
        if (pszConstraintType == nullptr)
9842
0
            continue;
9843
0
        const char *pszValue = oResultTable->GetValue(1, iRecord);
9844
0
        const char *pszMin = oResultTable->GetValue(2, iRecord);
9845
0
        const bool bIsMinIncluded =
9846
0
            oResultTable->GetValueAsInteger(3, iRecord) == 1;
9847
0
        const char *pszMax = oResultTable->GetValue(4, iRecord);
9848
0
        const bool bIsMaxIncluded =
9849
0
            oResultTable->GetValueAsInteger(5, iRecord) == 1;
9850
0
        const char *pszDescription = oResultTable->GetValue(6, iRecord);
9851
0
        const char *pszConstraintName = oResultTable->GetValue(7, iRecord);
9852
9853
0
        if (!osLastConstraintType.empty() && osLastConstraintType != "enum")
9854
0
        {
9855
0
            CPLError(CE_Failure, CPLE_AppDefined,
9856
0
                     "Only constraint of type 'enum' can have multiple rows");
9857
0
            error = true;
9858
0
            break;
9859
0
        }
9860
9861
0
        if (strcmp(pszConstraintType, "enum") == 0)
9862
0
        {
9863
0
            if (pszValue == nullptr)
9864
0
            {
9865
0
                CPLError(CE_Failure, CPLE_AppDefined,
9866
0
                         "NULL in 'value' column of enumeration");
9867
0
                error = true;
9868
0
                break;
9869
0
            }
9870
0
            if (osDescrConstraintName == pszConstraintName)
9871
0
            {
9872
0
                if (pszDescription)
9873
0
                {
9874
0
                    osConstraintDescription = pszDescription;
9875
0
                }
9876
0
                continue;
9877
0
            }
9878
0
            if (asValues.empty())
9879
0
            {
9880
0
                asValues.reserve(oResultTable->RowCount() + 1);
9881
0
            }
9882
0
            OGRCodedValue cv;
9883
            // intended: the 'value' column in GPKG is actually the code
9884
0
            cv.pszCode = VSI_STRDUP_VERBOSE(pszValue);
9885
0
            if (cv.pszCode == nullptr)
9886
0
            {
9887
0
                error = true;
9888
0
                break;
9889
0
            }
9890
0
            if (pszDescription)
9891
0
            {
9892
0
                cv.pszValue = VSI_STRDUP_VERBOSE(pszDescription);
9893
0
                if (cv.pszValue == nullptr)
9894
0
                {
9895
0
                    VSIFree(cv.pszCode);
9896
0
                    error = true;
9897
0
                    break;
9898
0
                }
9899
0
            }
9900
0
            else
9901
0
            {
9902
0
                cv.pszValue = nullptr;
9903
0
            }
9904
9905
            // If we can't get the data type from field definition, guess it
9906
            // from code.
9907
0
            if (nFieldType < 0 && nFieldTypeFromEnumCode != OFTString)
9908
0
            {
9909
0
                switch (CPLGetValueType(cv.pszCode))
9910
0
                {
9911
0
                    case CPL_VALUE_INTEGER:
9912
0
                    {
9913
0
                        if (nFieldTypeFromEnumCode != OFTReal &&
9914
0
                            nFieldTypeFromEnumCode != OFTInteger64)
9915
0
                        {
9916
0
                            const auto nVal = CPLAtoGIntBig(cv.pszCode);
9917
0
                            if (nVal < std::numeric_limits<int>::min() ||
9918
0
                                nVal > std::numeric_limits<int>::max())
9919
0
                            {
9920
0
                                nFieldTypeFromEnumCode = OFTInteger64;
9921
0
                            }
9922
0
                            else
9923
0
                            {
9924
0
                                nFieldTypeFromEnumCode = OFTInteger;
9925
0
                            }
9926
0
                        }
9927
0
                        break;
9928
0
                    }
9929
9930
0
                    case CPL_VALUE_REAL:
9931
0
                        nFieldTypeFromEnumCode = OFTReal;
9932
0
                        break;
9933
9934
0
                    case CPL_VALUE_STRING:
9935
0
                        nFieldTypeFromEnumCode = OFTString;
9936
0
                        break;
9937
0
                }
9938
0
            }
9939
9940
0
            asValues.emplace_back(cv);
9941
0
        }
9942
0
        else if (strcmp(pszConstraintType, "range") == 0)
9943
0
        {
9944
0
            OGRField sMin;
9945
0
            OGRField sMax;
9946
0
            OGR_RawField_SetUnset(&sMin);
9947
0
            OGR_RawField_SetUnset(&sMax);
9948
0
            if (nFieldType != OFTInteger && nFieldType != OFTInteger64)
9949
0
                nFieldType = OFTReal;
9950
0
            if (pszMin != nullptr &&
9951
0
                CPLAtof(pszMin) != -std::numeric_limits<double>::infinity())
9952
0
            {
9953
0
                if (nFieldType == OFTInteger)
9954
0
                    sMin.Integer = atoi(pszMin);
9955
0
                else if (nFieldType == OFTInteger64)
9956
0
                    sMin.Integer64 = CPLAtoGIntBig(pszMin);
9957
0
                else /* if( nFieldType == OFTReal ) */
9958
0
                    sMin.Real = CPLAtof(pszMin);
9959
0
            }
9960
0
            if (pszMax != nullptr &&
9961
0
                CPLAtof(pszMax) != std::numeric_limits<double>::infinity())
9962
0
            {
9963
0
                if (nFieldType == OFTInteger)
9964
0
                    sMax.Integer = atoi(pszMax);
9965
0
                else if (nFieldType == OFTInteger64)
9966
0
                    sMax.Integer64 = CPLAtoGIntBig(pszMax);
9967
0
                else /* if( nFieldType == OFTReal ) */
9968
0
                    sMax.Real = CPLAtof(pszMax);
9969
0
            }
9970
0
            poDomain = std::make_unique<OGRRangeFieldDomain>(
9971
0
                name, pszDescription ? pszDescription : "",
9972
0
                static_cast<OGRFieldType>(nFieldType), eSubType, sMin,
9973
0
                bIsMinIncluded, sMax, bIsMaxIncluded);
9974
0
        }
9975
0
        else if (strcmp(pszConstraintType, "glob") == 0)
9976
0
        {
9977
0
            if (pszValue == nullptr)
9978
0
            {
9979
0
                CPLError(CE_Failure, CPLE_AppDefined,
9980
0
                         "NULL in 'value' column of glob");
9981
0
                error = true;
9982
0
                break;
9983
0
            }
9984
0
            if (nFieldType < 0)
9985
0
                nFieldType = OFTString;
9986
0
            poDomain = std::make_unique<OGRGlobFieldDomain>(
9987
0
                name, pszDescription ? pszDescription : "",
9988
0
                static_cast<OGRFieldType>(nFieldType), eSubType, pszValue);
9989
0
        }
9990
0
        else
9991
0
        {
9992
0
            CPLError(CE_Failure, CPLE_AppDefined,
9993
0
                     "Unhandled constraint_type: %s", pszConstraintType);
9994
0
            error = true;
9995
0
            break;
9996
0
        }
9997
9998
0
        osLastConstraintType = pszConstraintType;
9999
0
    }
10000
10001
0
    if (!asValues.empty())
10002
0
    {
10003
0
        if (nFieldType < 0)
10004
0
            nFieldType = nFieldTypeFromEnumCode;
10005
0
        poDomain = std::make_unique<OGRCodedFieldDomain>(
10006
0
            name, osConstraintDescription,
10007
0
            static_cast<OGRFieldType>(nFieldType), eSubType,
10008
0
            std::move(asValues));
10009
0
    }
10010
10011
0
    if (error)
10012
0
    {
10013
0
        return nullptr;
10014
0
    }
10015
10016
0
    m_oMapFieldDomains[name] = std::move(poDomain);
10017
0
    return GDALDataset::GetFieldDomain(name);
10018
0
}
10019
10020
/************************************************************************/
10021
/*                           AddFieldDomain()                           */
10022
/************************************************************************/
10023
10024
bool GDALGeoPackageDataset::AddFieldDomain(
10025
    std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10026
0
{
10027
0
    const std::string domainName(domain->GetName());
10028
0
    if (!GetUpdate())
10029
0
    {
10030
0
        CPLError(CE_Failure, CPLE_NotSupported,
10031
0
                 "AddFieldDomain() not supported on read-only dataset");
10032
0
        return false;
10033
0
    }
10034
0
    if (GetFieldDomain(domainName) != nullptr)
10035
0
    {
10036
0
        failureReason = "A domain of identical name already exists";
10037
0
        return false;
10038
0
    }
10039
0
    if (!CreateColumnsTableAndColumnConstraintsTablesIfNecessary())
10040
0
        return false;
10041
10042
0
    const bool bIsGPKG10 = HasDataColumnConstraintsTableGPKG_1_0();
10043
0
    const char *min_is_inclusive =
10044
0
        bIsGPKG10 ? "minIsInclusive" : "min_is_inclusive";
10045
0
    const char *max_is_inclusive =
10046
0
        bIsGPKG10 ? "maxIsInclusive" : "max_is_inclusive";
10047
10048
0
    const auto &osDescription = domain->GetDescription();
10049
0
    switch (domain->GetDomainType())
10050
0
    {
10051
0
        case OFDT_CODED:
10052
0
        {
10053
0
            const auto poCodedDomain =
10054
0
                cpl::down_cast<const OGRCodedFieldDomain *>(domain.get());
10055
0
            if (!osDescription.empty())
10056
0
            {
10057
                // We use a little trick by using a dummy
10058
                // _{domainname}_domain_description enum that has a single
10059
                // entry whose description is the description of the main
10060
                // domain.
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_domain_description', 'enum', '', NULL, NULL, NULL, "
10067
0
                    "NULL, %Q)",
10068
0
                    min_is_inclusive, max_is_inclusive, domainName.c_str(),
10069
0
                    osDescription.c_str());
10070
0
                CPL_IGNORE_RET_VAL(SQLCommand(hDB, pszSQL));
10071
0
                sqlite3_free(pszSQL);
10072
0
            }
10073
0
            const auto &enumeration = poCodedDomain->GetEnumeration();
10074
0
            for (int i = 0; enumeration[i].pszCode != nullptr; ++i)
10075
0
            {
10076
0
                char *pszSQL = sqlite3_mprintf(
10077
0
                    "INSERT INTO gpkg_data_column_constraints ("
10078
0
                    "constraint_name, constraint_type, value, "
10079
0
                    "min, %s, max, %s, "
10080
0
                    "description) VALUES ("
10081
0
                    "'%q', 'enum', '%q', NULL, NULL, NULL, NULL, %Q)",
10082
0
                    min_is_inclusive, max_is_inclusive, domainName.c_str(),
10083
0
                    enumeration[i].pszCode, enumeration[i].pszValue);
10084
0
                bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10085
0
                sqlite3_free(pszSQL);
10086
0
                if (!ok)
10087
0
                    return false;
10088
0
            }
10089
0
            break;
10090
0
        }
10091
10092
0
        case OFDT_RANGE:
10093
0
        {
10094
0
            const auto poRangeDomain =
10095
0
                cpl::down_cast<const OGRRangeFieldDomain *>(domain.get());
10096
0
            const auto eFieldType = poRangeDomain->GetFieldType();
10097
0
            if (eFieldType != OFTInteger && eFieldType != OFTInteger64 &&
10098
0
                eFieldType != OFTReal)
10099
0
            {
10100
0
                failureReason = "Only range domains of numeric type are "
10101
0
                                "supported in GeoPackage";
10102
0
                return false;
10103
0
            }
10104
10105
0
            double dfMin = -std::numeric_limits<double>::infinity();
10106
0
            double dfMax = std::numeric_limits<double>::infinity();
10107
0
            bool bMinIsInclusive = true;
10108
0
            const auto &sMin = poRangeDomain->GetMin(bMinIsInclusive);
10109
0
            bool bMaxIsInclusive = true;
10110
0
            const auto &sMax = poRangeDomain->GetMax(bMaxIsInclusive);
10111
0
            if (eFieldType == OFTInteger)
10112
0
            {
10113
0
                if (!OGR_RawField_IsUnset(&sMin))
10114
0
                    dfMin = sMin.Integer;
10115
0
                if (!OGR_RawField_IsUnset(&sMax))
10116
0
                    dfMax = sMax.Integer;
10117
0
            }
10118
0
            else if (eFieldType == OFTInteger64)
10119
0
            {
10120
0
                if (!OGR_RawField_IsUnset(&sMin))
10121
0
                    dfMin = static_cast<double>(sMin.Integer64);
10122
0
                if (!OGR_RawField_IsUnset(&sMax))
10123
0
                    dfMax = static_cast<double>(sMax.Integer64);
10124
0
            }
10125
0
            else /* if( eFieldType == OFTReal ) */
10126
0
            {
10127
0
                if (!OGR_RawField_IsUnset(&sMin))
10128
0
                    dfMin = sMin.Real;
10129
0
                if (!OGR_RawField_IsUnset(&sMax))
10130
0
                    dfMax = sMax.Real;
10131
0
            }
10132
10133
0
            sqlite3_stmt *hInsertStmt = nullptr;
10134
0
            const char *pszSQL =
10135
0
                CPLSPrintf("INSERT INTO gpkg_data_column_constraints ("
10136
0
                           "constraint_name, constraint_type, value, "
10137
0
                           "min, %s, max, %s, "
10138
0
                           "description) VALUES ("
10139
0
                           "?, 'range', NULL, ?, ?, ?, ?, ?)",
10140
0
                           min_is_inclusive, max_is_inclusive);
10141
0
            if (SQLPrepareWithError(hDB, pszSQL, -1, &hInsertStmt, nullptr) !=
10142
0
                SQLITE_OK)
10143
0
            {
10144
0
                return false;
10145
0
            }
10146
0
            sqlite3_bind_text(hInsertStmt, 1, domainName.c_str(),
10147
0
                              static_cast<int>(domainName.size()),
10148
0
                              SQLITE_TRANSIENT);
10149
0
            sqlite3_bind_double(hInsertStmt, 2, dfMin);
10150
0
            sqlite3_bind_int(hInsertStmt, 3, bMinIsInclusive ? 1 : 0);
10151
0
            sqlite3_bind_double(hInsertStmt, 4, dfMax);
10152
0
            sqlite3_bind_int(hInsertStmt, 5, bMaxIsInclusive ? 1 : 0);
10153
0
            if (osDescription.empty())
10154
0
            {
10155
0
                sqlite3_bind_null(hInsertStmt, 6);
10156
0
            }
10157
0
            else
10158
0
            {
10159
0
                sqlite3_bind_text(hInsertStmt, 6, osDescription.c_str(),
10160
0
                                  static_cast<int>(osDescription.size()),
10161
0
                                  SQLITE_TRANSIENT);
10162
0
            }
10163
0
            const int sqlite_err = sqlite3_step(hInsertStmt);
10164
0
            sqlite3_finalize(hInsertStmt);
10165
0
            if (sqlite_err != SQLITE_OK && sqlite_err != SQLITE_DONE)
10166
0
            {
10167
0
                CPLError(CE_Failure, CPLE_AppDefined,
10168
0
                         "failed to execute insertion '%s': %s", pszSQL,
10169
0
                         sqlite3_errmsg(hDB));
10170
0
                return false;
10171
0
            }
10172
10173
0
            break;
10174
0
        }
10175
10176
0
        case OFDT_GLOB:
10177
0
        {
10178
0
            const auto poGlobDomain =
10179
0
                cpl::down_cast<const OGRGlobFieldDomain *>(domain.get());
10180
0
            char *pszSQL = sqlite3_mprintf(
10181
0
                "INSERT INTO gpkg_data_column_constraints ("
10182
0
                "constraint_name, constraint_type, value, "
10183
0
                "min, %s, max, %s, "
10184
0
                "description) VALUES ("
10185
0
                "'%q', 'glob', '%q', NULL, NULL, NULL, NULL, %Q)",
10186
0
                min_is_inclusive, max_is_inclusive, domainName.c_str(),
10187
0
                poGlobDomain->GetGlob().c_str(),
10188
0
                osDescription.empty() ? nullptr : osDescription.c_str());
10189
0
            bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10190
0
            sqlite3_free(pszSQL);
10191
0
            if (!ok)
10192
0
                return false;
10193
10194
0
            break;
10195
0
        }
10196
0
    }
10197
10198
0
    m_oMapFieldDomains[domainName] = std::move(domain);
10199
0
    return true;
10200
0
}
10201
10202
/************************************************************************/
10203
/*                        UpdateFieldDomain()                           */
10204
/************************************************************************/
10205
10206
bool GDALGeoPackageDataset::UpdateFieldDomain(
10207
    std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
10208
0
{
10209
0
    const std::string domainName(domain->GetName());
10210
0
    if (eAccess != GA_Update)
10211
0
    {
10212
0
        CPLError(CE_Failure, CPLE_NotSupported,
10213
0
                 "UpdateFieldDomain() not supported on read-only dataset");
10214
0
        return false;
10215
0
    }
10216
10217
0
    if (GetFieldDomain(domainName) == nullptr)
10218
0
    {
10219
0
        failureReason = "The domain should already exist to be updated";
10220
0
        return false;
10221
0
    }
10222
10223
0
    bool bRet = SoftStartTransaction() == OGRERR_NONE;
10224
0
    if (bRet)
10225
0
    {
10226
0
        bRet = DeleteFieldDomain(domainName, failureReason) &&
10227
0
               AddFieldDomain(std::move(domain), failureReason);
10228
0
        if (bRet)
10229
0
            bRet = SoftCommitTransaction() == OGRERR_NONE;
10230
0
        else
10231
0
            SoftRollbackTransaction();
10232
0
    }
10233
0
    return bRet;
10234
0
}
10235
10236
/************************************************************************/
10237
/*                         DeleteFieldDomain()                          */
10238
/************************************************************************/
10239
10240
bool GDALGeoPackageDataset::DeleteFieldDomain(const std::string &name,
10241
                                              std::string &failureReason)
10242
0
{
10243
0
    if (eAccess != GA_Update)
10244
0
    {
10245
0
        CPLError(CE_Failure, CPLE_NotSupported,
10246
0
                 "DeleteFieldDomain() not supported on read-only dataset");
10247
0
        return false;
10248
0
    }
10249
0
    if (GetFieldDomain(name) == nullptr)
10250
0
    {
10251
0
        failureReason = "Domain does not exist";
10252
0
        return false;
10253
0
    }
10254
10255
0
    char *pszSQL =
10256
0
        sqlite3_mprintf("DELETE FROM gpkg_data_column_constraints WHERE "
10257
0
                        "constraint_name IN ('%q', '_%q_domain_description')",
10258
0
                        name.c_str(), name.c_str());
10259
0
    const bool ok = SQLCommand(hDB, pszSQL) == OGRERR_NONE;
10260
0
    sqlite3_free(pszSQL);
10261
0
    if (ok)
10262
0
        m_oMapFieldDomains.erase(name);
10263
0
    return ok;
10264
0
}
10265
10266
/************************************************************************/
10267
/*                          AddRelationship()                           */
10268
/************************************************************************/
10269
10270
bool GDALGeoPackageDataset::AddRelationship(
10271
    std::unique_ptr<GDALRelationship> &&relationship,
10272
    std::string &failureReason)
10273
0
{
10274
0
    if (!GetUpdate())
10275
0
    {
10276
0
        CPLError(CE_Failure, CPLE_NotSupported,
10277
0
                 "AddRelationship() not supported on read-only dataset");
10278
0
        return false;
10279
0
    }
10280
10281
0
    const std::string osRelationshipName = GenerateNameForRelationship(
10282
0
        relationship->GetLeftTableName().c_str(),
10283
0
        relationship->GetRightTableName().c_str(),
10284
0
        relationship->GetRelatedTableType().c_str());
10285
    // sanity checks
10286
0
    if (GetRelationship(osRelationshipName) != nullptr)
10287
0
    {
10288
0
        failureReason = "A relationship of identical name already exists";
10289
0
        return false;
10290
0
    }
10291
10292
0
    if (!ValidateRelationship(relationship.get(), failureReason))
10293
0
    {
10294
0
        return false;
10295
0
    }
10296
10297
0
    if (CreateExtensionsTableIfNecessary() != OGRERR_NONE)
10298
0
    {
10299
0
        return false;
10300
0
    }
10301
0
    if (!CreateRelationsTableIfNecessary())
10302
0
    {
10303
0
        failureReason = "Could not create gpkgext_relations table";
10304
0
        return false;
10305
0
    }
10306
0
    if (SQLGetInteger(GetDB(),
10307
0
                      "SELECT 1 FROM gpkg_extensions WHERE "
10308
0
                      "table_name = 'gpkgext_relations'",
10309
0
                      nullptr) != 1)
10310
0
    {
10311
0
        if (OGRERR_NONE !=
10312
0
            SQLCommand(
10313
0
                GetDB(),
10314
0
                "INSERT INTO gpkg_extensions "
10315
0
                "(table_name,column_name,extension_name,definition,scope) "
10316
0
                "VALUES ('gpkgext_relations', NULL, 'gpkg_related_tables', "
10317
0
                "'http://www.geopackage.org/18-000.html', "
10318
0
                "'read-write')"))
10319
0
        {
10320
0
            failureReason =
10321
0
                "Could not create gpkg_extensions entry for gpkgext_relations";
10322
0
            return false;
10323
0
        }
10324
0
    }
10325
10326
0
    const std::string &osLeftTableName = relationship->GetLeftTableName();
10327
0
    const std::string &osRightTableName = relationship->GetRightTableName();
10328
0
    const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10329
0
    const auto &aosRightTableFields = relationship->GetRightTableFields();
10330
10331
0
    std::string osRelatedTableType = relationship->GetRelatedTableType();
10332
0
    if (osRelatedTableType.empty())
10333
0
    {
10334
0
        osRelatedTableType = "features";
10335
0
    }
10336
10337
    // generate mapping table if not set
10338
0
    CPLString osMappingTableName = relationship->GetMappingTableName();
10339
0
    if (osMappingTableName.empty())
10340
0
    {
10341
0
        int nIndex = 1;
10342
0
        osMappingTableName = osLeftTableName + "_" + osRightTableName;
10343
0
        while (FindLayerIndex(osMappingTableName.c_str()) >= 0)
10344
0
        {
10345
0
            nIndex += 1;
10346
0
            osMappingTableName.Printf("%s_%s_%d", osLeftTableName.c_str(),
10347
0
                                      osRightTableName.c_str(), nIndex);
10348
0
        }
10349
10350
        // determine whether base/related keys are unique
10351
0
        bool bBaseKeyIsUnique = false;
10352
0
        {
10353
0
            const std::set<std::string> uniqueBaseFieldsUC =
10354
0
                SQLGetUniqueFieldUCConstraints(GetDB(),
10355
0
                                               osLeftTableName.c_str());
10356
0
            if (uniqueBaseFieldsUC.find(
10357
0
                    CPLString(aosLeftTableFields[0]).toupper()) !=
10358
0
                uniqueBaseFieldsUC.end())
10359
0
            {
10360
0
                bBaseKeyIsUnique = true;
10361
0
            }
10362
0
        }
10363
0
        bool bRelatedKeyIsUnique = false;
10364
0
        {
10365
0
            const std::set<std::string> uniqueRelatedFieldsUC =
10366
0
                SQLGetUniqueFieldUCConstraints(GetDB(),
10367
0
                                               osRightTableName.c_str());
10368
0
            if (uniqueRelatedFieldsUC.find(
10369
0
                    CPLString(aosRightTableFields[0]).toupper()) !=
10370
0
                uniqueRelatedFieldsUC.end())
10371
0
            {
10372
0
                bRelatedKeyIsUnique = true;
10373
0
            }
10374
0
        }
10375
10376
        // create mapping table
10377
10378
0
        std::string osBaseIdDefinition = "base_id INTEGER";
10379
0
        if (bBaseKeyIsUnique)
10380
0
        {
10381
0
            char *pszSQL = sqlite3_mprintf(
10382
0
                " CONSTRAINT 'fk_base_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10383
0
                "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10384
0
                "DEFERRED",
10385
0
                osMappingTableName.c_str(), osLeftTableName.c_str(),
10386
0
                aosLeftTableFields[0].c_str());
10387
0
            osBaseIdDefinition += pszSQL;
10388
0
            sqlite3_free(pszSQL);
10389
0
        }
10390
10391
0
        std::string osRelatedIdDefinition = "related_id INTEGER";
10392
0
        if (bRelatedKeyIsUnique)
10393
0
        {
10394
0
            char *pszSQL = sqlite3_mprintf(
10395
0
                " CONSTRAINT 'fk_related_id_%q' REFERENCES \"%w\"(\"%w\") ON "
10396
0
                "DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY "
10397
0
                "DEFERRED",
10398
0
                osMappingTableName.c_str(), osRightTableName.c_str(),
10399
0
                aosRightTableFields[0].c_str());
10400
0
            osRelatedIdDefinition += pszSQL;
10401
0
            sqlite3_free(pszSQL);
10402
0
        }
10403
10404
0
        char *pszSQL = sqlite3_mprintf("CREATE TABLE \"%w\" ("
10405
0
                                       "id INTEGER PRIMARY KEY AUTOINCREMENT, "
10406
0
                                       "%s, %s);",
10407
0
                                       osMappingTableName.c_str(),
10408
0
                                       osBaseIdDefinition.c_str(),
10409
0
                                       osRelatedIdDefinition.c_str());
10410
0
        OGRErr eErr = SQLCommand(hDB, pszSQL);
10411
0
        sqlite3_free(pszSQL);
10412
0
        if (eErr != OGRERR_NONE)
10413
0
        {
10414
0
            failureReason =
10415
0
                ("Could not create mapping table " + osMappingTableName)
10416
0
                    .c_str();
10417
0
            return false;
10418
0
        }
10419
10420
        /*
10421
         * Strictly speaking we should NOT be inserting the mapping table into gpkg_contents.
10422
         * The related tables extension explicitly states that the mapping table should only be
10423
         * in the gpkgext_relations table and not in gpkg_contents. (See also discussion at
10424
         * https://github.com/opengeospatial/geopackage/issues/679).
10425
         *
10426
         * However, if we don't insert the mapping table into gpkg_contents then it is no longer
10427
         * visible to some clients (eg ESRI software only allows opening tables that are present
10428
         * in gpkg_contents). So we'll do this anyway, for maximum compatibility and flexibility.
10429
         *
10430
         * More related discussion is at https://github.com/OSGeo/gdal/pull/9258
10431
         */
10432
0
        pszSQL = sqlite3_mprintf(
10433
0
            "INSERT INTO gpkg_contents "
10434
0
            "(table_name,data_type,identifier,description,last_change,srs_id) "
10435
0
            "VALUES "
10436
0
            "('%q','attributes','%q','Mapping table for relationship between "
10437
0
            "%q and %q',%s,0)",
10438
0
            osMappingTableName.c_str(), /*table_name*/
10439
0
            osMappingTableName.c_str(), /*identifier*/
10440
0
            osLeftTableName.c_str(),    /*description left table name*/
10441
0
            osRightTableName.c_str(),   /*description right table name*/
10442
0
            GDALGeoPackageDataset::GetCurrentDateEscapedSQL().c_str());
10443
10444
        // Note -- we explicitly ignore failures here, because hey, we aren't really
10445
        // supposed to be adding this table to gpkg_contents anyway!
10446
0
        (void)SQLCommand(hDB, pszSQL);
10447
0
        sqlite3_free(pszSQL);
10448
10449
0
        pszSQL = sqlite3_mprintf(
10450
0
            "CREATE INDEX \"idx_%w_base_id\" ON \"%w\" (base_id);",
10451
0
            osMappingTableName.c_str(), osMappingTableName.c_str());
10452
0
        eErr = SQLCommand(hDB, pszSQL);
10453
0
        sqlite3_free(pszSQL);
10454
0
        if (eErr != OGRERR_NONE)
10455
0
        {
10456
0
            failureReason = ("Could not create index for " +
10457
0
                             osMappingTableName + " (base_id)")
10458
0
                                .c_str();
10459
0
            return false;
10460
0
        }
10461
10462
0
        pszSQL = sqlite3_mprintf(
10463
0
            "CREATE INDEX \"idx_%qw_related_id\" ON \"%w\" (related_id);",
10464
0
            osMappingTableName.c_str(), osMappingTableName.c_str());
10465
0
        eErr = SQLCommand(hDB, pszSQL);
10466
0
        sqlite3_free(pszSQL);
10467
0
        if (eErr != OGRERR_NONE)
10468
0
        {
10469
0
            failureReason = ("Could not create index for " +
10470
0
                             osMappingTableName + " (related_id)")
10471
0
                                .c_str();
10472
0
            return false;
10473
0
        }
10474
0
    }
10475
0
    else
10476
0
    {
10477
        // validate mapping table structure
10478
0
        if (OGRGeoPackageTableLayer *poLayer =
10479
0
                cpl::down_cast<OGRGeoPackageTableLayer *>(
10480
0
                    GetLayerByName(osMappingTableName)))
10481
0
        {
10482
0
            if (poLayer->GetLayerDefn()->GetFieldIndex("base_id") < 0)
10483
0
            {
10484
0
                failureReason =
10485
0
                    ("Field base_id must exist in " + osMappingTableName)
10486
0
                        .c_str();
10487
0
                return false;
10488
0
            }
10489
0
            if (poLayer->GetLayerDefn()->GetFieldIndex("related_id") < 0)
10490
0
            {
10491
0
                failureReason =
10492
0
                    ("Field related_id must exist in " + osMappingTableName)
10493
0
                        .c_str();
10494
0
                return false;
10495
0
            }
10496
0
        }
10497
0
        else
10498
0
        {
10499
0
            failureReason =
10500
0
                ("Could not retrieve table " + osMappingTableName).c_str();
10501
0
            return false;
10502
0
        }
10503
0
    }
10504
10505
0
    char *pszSQL = sqlite3_mprintf(
10506
0
        "INSERT INTO gpkg_extensions "
10507
0
        "(table_name,column_name,extension_name,definition,scope) "
10508
0
        "VALUES ('%q', NULL, 'gpkg_related_tables', "
10509
0
        "'http://www.geopackage.org/18-000.html', "
10510
0
        "'read-write')",
10511
0
        osMappingTableName.c_str());
10512
0
    OGRErr eErr = SQLCommand(hDB, pszSQL);
10513
0
    sqlite3_free(pszSQL);
10514
0
    if (eErr != OGRERR_NONE)
10515
0
    {
10516
0
        failureReason = ("Could not insert mapping table " +
10517
0
                         osMappingTableName + " into gpkg_extensions")
10518
0
                            .c_str();
10519
0
        return false;
10520
0
    }
10521
10522
0
    pszSQL = sqlite3_mprintf(
10523
0
        "INSERT INTO gpkgext_relations "
10524
0
        "(base_table_name,base_primary_column,related_table_name,related_"
10525
0
        "primary_column,relation_name,mapping_table_name) "
10526
0
        "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10527
0
        osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10528
0
        osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10529
0
        osRelatedTableType.c_str(), osMappingTableName.c_str());
10530
0
    eErr = SQLCommand(hDB, pszSQL);
10531
0
    sqlite3_free(pszSQL);
10532
0
    if (eErr != OGRERR_NONE)
10533
0
    {
10534
0
        failureReason = "Could not insert relationship into gpkgext_relations";
10535
0
        return false;
10536
0
    }
10537
10538
0
    ClearCachedRelationships();
10539
0
    LoadRelationships();
10540
0
    return true;
10541
0
}
10542
10543
/************************************************************************/
10544
/*                         DeleteRelationship()                         */
10545
/************************************************************************/
10546
10547
bool GDALGeoPackageDataset::DeleteRelationship(const std::string &name,
10548
                                               std::string &failureReason)
10549
0
{
10550
0
    if (eAccess != GA_Update)
10551
0
    {
10552
0
        CPLError(CE_Failure, CPLE_NotSupported,
10553
0
                 "DeleteRelationship() not supported on read-only dataset");
10554
0
        return false;
10555
0
    }
10556
10557
    // ensure relationships are up to date before we try to remove one
10558
0
    ClearCachedRelationships();
10559
0
    LoadRelationships();
10560
10561
0
    std::string osMappingTableName;
10562
0
    {
10563
0
        const GDALRelationship *poRelationship = GetRelationship(name);
10564
0
        if (poRelationship == nullptr)
10565
0
        {
10566
0
            failureReason = "Could not find relationship with name " + name;
10567
0
            return false;
10568
0
        }
10569
10570
0
        osMappingTableName = poRelationship->GetMappingTableName();
10571
0
    }
10572
10573
    // DeleteLayerCommon will delete existing relationship objects, so we can't
10574
    // refer to poRelationship or any of its members previously obtained here
10575
0
    if (DeleteLayerCommon(osMappingTableName.c_str()) != OGRERR_NONE)
10576
0
    {
10577
0
        failureReason =
10578
0
            "Could not remove mapping layer name " + osMappingTableName;
10579
10580
        // relationships may have been left in an inconsistent state -- reload
10581
        // them now
10582
0
        ClearCachedRelationships();
10583
0
        LoadRelationships();
10584
0
        return false;
10585
0
    }
10586
10587
0
    ClearCachedRelationships();
10588
0
    LoadRelationships();
10589
0
    return true;
10590
0
}
10591
10592
/************************************************************************/
10593
/*                        UpdateRelationship()                          */
10594
/************************************************************************/
10595
10596
bool GDALGeoPackageDataset::UpdateRelationship(
10597
    std::unique_ptr<GDALRelationship> &&relationship,
10598
    std::string &failureReason)
10599
0
{
10600
0
    if (eAccess != GA_Update)
10601
0
    {
10602
0
        CPLError(CE_Failure, CPLE_NotSupported,
10603
0
                 "UpdateRelationship() not supported on read-only dataset");
10604
0
        return false;
10605
0
    }
10606
10607
    // ensure relationships are up to date before we try to update one
10608
0
    ClearCachedRelationships();
10609
0
    LoadRelationships();
10610
10611
0
    const std::string &osRelationshipName = relationship->GetName();
10612
0
    const std::string &osLeftTableName = relationship->GetLeftTableName();
10613
0
    const std::string &osRightTableName = relationship->GetRightTableName();
10614
0
    const std::string &osMappingTableName = relationship->GetMappingTableName();
10615
0
    const auto &aosLeftTableFields = relationship->GetLeftTableFields();
10616
0
    const auto &aosRightTableFields = relationship->GetRightTableFields();
10617
10618
    // sanity checks
10619
0
    {
10620
0
        const GDALRelationship *poExistingRelationship =
10621
0
            GetRelationship(osRelationshipName);
10622
0
        if (poExistingRelationship == nullptr)
10623
0
        {
10624
0
            failureReason =
10625
0
                "The relationship should already exist to be updated";
10626
0
            return false;
10627
0
        }
10628
10629
0
        if (!ValidateRelationship(relationship.get(), failureReason))
10630
0
        {
10631
0
            return false;
10632
0
        }
10633
10634
        // we don't permit changes to the participating tables
10635
0
        if (osLeftTableName != poExistingRelationship->GetLeftTableName())
10636
0
        {
10637
0
            failureReason = ("Cannot change base table from " +
10638
0
                             poExistingRelationship->GetLeftTableName() +
10639
0
                             " to " + osLeftTableName)
10640
0
                                .c_str();
10641
0
            return false;
10642
0
        }
10643
0
        if (osRightTableName != poExistingRelationship->GetRightTableName())
10644
0
        {
10645
0
            failureReason = ("Cannot change related table from " +
10646
0
                             poExistingRelationship->GetRightTableName() +
10647
0
                             " to " + osRightTableName)
10648
0
                                .c_str();
10649
0
            return false;
10650
0
        }
10651
0
        if (osMappingTableName != poExistingRelationship->GetMappingTableName())
10652
0
        {
10653
0
            failureReason = ("Cannot change mapping table from " +
10654
0
                             poExistingRelationship->GetMappingTableName() +
10655
0
                             " to " + osMappingTableName)
10656
0
                                .c_str();
10657
0
            return false;
10658
0
        }
10659
0
    }
10660
10661
0
    std::string osRelatedTableType = relationship->GetRelatedTableType();
10662
0
    if (osRelatedTableType.empty())
10663
0
    {
10664
0
        osRelatedTableType = "features";
10665
0
    }
10666
10667
0
    char *pszSQL = sqlite3_mprintf(
10668
0
        "DELETE FROM gpkgext_relations WHERE mapping_table_name='%q'",
10669
0
        osMappingTableName.c_str());
10670
0
    OGRErr eErr = SQLCommand(hDB, pszSQL);
10671
0
    sqlite3_free(pszSQL);
10672
0
    if (eErr != OGRERR_NONE)
10673
0
    {
10674
0
        failureReason =
10675
0
            "Could not delete old relationship from gpkgext_relations";
10676
0
        return false;
10677
0
    }
10678
10679
0
    pszSQL = sqlite3_mprintf(
10680
0
        "INSERT INTO gpkgext_relations "
10681
0
        "(base_table_name,base_primary_column,related_table_name,related_"
10682
0
        "primary_column,relation_name,mapping_table_name) "
10683
0
        "VALUES ('%q', '%q', '%q', '%q', '%q', '%q')",
10684
0
        osLeftTableName.c_str(), aosLeftTableFields[0].c_str(),
10685
0
        osRightTableName.c_str(), aosRightTableFields[0].c_str(),
10686
0
        osRelatedTableType.c_str(), osMappingTableName.c_str());
10687
0
    eErr = SQLCommand(hDB, pszSQL);
10688
0
    sqlite3_free(pszSQL);
10689
0
    if (eErr != OGRERR_NONE)
10690
0
    {
10691
0
        failureReason =
10692
0
            "Could not insert updated relationship into gpkgext_relations";
10693
0
        return false;
10694
0
    }
10695
10696
0
    ClearCachedRelationships();
10697
0
    LoadRelationships();
10698
0
    return true;
10699
0
}
10700
10701
/************************************************************************/
10702
/*                    GetSqliteMasterContent()                          */
10703
/************************************************************************/
10704
10705
const std::vector<SQLSqliteMasterContent> &
10706
GDALGeoPackageDataset::GetSqliteMasterContent()
10707
24
{
10708
24
    if (m_aoSqliteMasterContent.empty())
10709
24
    {
10710
24
        auto oResultTable =
10711
24
            SQLQuery(hDB, "SELECT sql, type, tbl_name FROM sqlite_master");
10712
24
        if (oResultTable)
10713
24
        {
10714
529
            for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
10715
505
            {
10716
505
                SQLSqliteMasterContent row;
10717
505
                const char *pszSQL = oResultTable->GetValue(0, rowCnt);
10718
505
                row.osSQL = pszSQL ? pszSQL : "";
10719
505
                const char *pszType = oResultTable->GetValue(1, rowCnt);
10720
505
                row.osType = pszType ? pszType : "";
10721
505
                const char *pszTableName = oResultTable->GetValue(2, rowCnt);
10722
505
                row.osTableName = pszTableName ? pszTableName : "";
10723
505
                m_aoSqliteMasterContent.emplace_back(std::move(row));
10724
505
            }
10725
24
        }
10726
24
    }
10727
24
    return m_aoSqliteMasterContent;
10728
24
}