diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index bf135f016386..a3b01588db2b 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -6951,6 +6951,13 @@ def test_ogr_gpkg_relations(tmp_vsimem, tmp_path): assert rel.GetRightMappingTableFields() == ["related_id"] assert rel.GetRelatedTableType() == "attributes" + # ensure that the mapping table, which is present in gpkgext_relations but + # NOT gpkg_contents can be opened as a layer + lyr = ds.GetLayer("my_mapping_table") + assert lyr is not None + assert lyr.GetLayerDefn().GetFieldDefn(0).GetName() == "base_id" + assert lyr.GetLayerDefn().GetFieldDefn(1).GetName() == "related_id" + lyr = ds.GetLayer("a") lyr.Rename("a_renamed") lyr.AlterFieldDefn( @@ -7212,6 +7219,21 @@ def get_query_row_count(query): ) == 1 ) + # force delete from gpkg_contents, and then ensure that we CAN successfully + # load layers which are present ONLY in gpkgext_relations but NOT + # gpkg_contents (i.e. datasources which follow the Related Tables specification + # exactly) + ds.ExecuteSQL( + "DELETE FROM gpkg_contents WHERE table_name='origin_table_dest_table'" + ) + + ds = gdal.OpenEx(filename, gdal.OF_VECTOR | gdal.OF_UPDATE) + # ensure that the mapping table, which is present in gpkgext_relations but + # NOT gpkg_contents can be opened as a layer + lyr = ds.GetLayer("origin_table_dest_table") + assert lyr is not None + assert lyr.GetLayerDefn().GetFieldDefn(0).GetName() == "base_id" + assert lyr.GetLayerDefn().GetFieldDefn(1).GetName() == "related_id" lyr = ds.CreateLayer("origin_table2", geom_type=ogr.wkbNone) fld_defn = ogr.FieldDefn("o_pkey", ogr.OFTInteger) diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index be1255630050..8184918e7822 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -1598,6 +1598,7 @@ int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo, CheckUnknownExtensions(); int bRet = FALSE; + bool bHasGPKGExtRelations = false; if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) { m_bHasGPKGGeometryColumns = @@ -1606,6 +1607,7 @@ int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo, "name = 'gpkg_geometry_columns' AND " "type IN ('table', 'view')", nullptr) == 1; + bHasGPKGExtRelations = HasGpkgextRelationsTable(); } if (m_bHasGPKGGeometryColumns) { @@ -1645,6 +1647,18 @@ int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo, bHasASpatialOrAttributes = (oResultTable && oResultTable->RowCount() == 1); } + if (bHasGPKGExtRelations) + { + osSQL += "UNION ALL " + "SELECT mapping_table_name, mapping_table_name, 0 as " + "is_spatial, NULL, NULL, 0, 0, 0 AS " + "xmin, 0 AS ymin, 0 AS xmax, 0 AS ymax, 0 AS " + "is_in_gpkg_contents, 'table' AS object_type " + "FROM gpkgext_relations WHERE " + "lower(mapping_table_name) NOT IN (SELECT " + "lower(table_name) FROM " + "gpkg_contents)"; + } if (EQUAL(pszListAllTables, "YES") || (!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO"))) { @@ -1663,6 +1677,12 @@ int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo, "'st_geometry_columns', 'geometry_columns') " "AND lower(name) NOT IN (SELECT lower(table_name) FROM " "gpkg_contents)"; + if (bHasGPKGExtRelations) + { + osSQL += " AND lower(name) NOT IN (SELECT " + "lower(mapping_table_name) FROM " + "gpkgext_relations)"; + } } const int nTableLimit = GetOGRTableLimit(); if (nTableLimit > 0)