Skip to content

Commit 288977e

Browse files
committed
[gpkg] Also read relationships defined using foreign key constraints
When reading relationships, always read relationships defined using foreign key constraints regardless of whether or not the related tables extension is in use. The related table extension only permits definition of many-to-many relationships, so there's a strong case for supporting one-to-many relationships defined outside of this extension. In fact it's what's recommended upstream: opengeospatial/geopackage#678 (comment)
1 parent 4926535 commit 288977e

File tree

4 files changed

+73
-14
lines changed

4 files changed

+73
-14
lines changed

autotest/ogr/ogr_gpkg.py

+27
Original file line numberDiff line numberDiff line change
@@ -7086,6 +7086,33 @@ def test_ogr_gpkg_relations(tmp_vsimem, tmp_path):
70867086
assert rel.GetRightMappingTableFields() == ["related_id"]
70877087
assert rel.GetRelatedTableType() == "features"
70887088

7089+
# a one-to-many relationship defined using foreign key constraints
7090+
ds = gdal.OpenEx(filename, gdal.OF_VECTOR | gdal.OF_UPDATE)
7091+
ds.ExecuteSQL(
7092+
"CREATE TABLE test_relation_a(artistid INTEGER PRIMARY KEY, artistname TEXT)"
7093+
)
7094+
ds.ExecuteSQL(
7095+
"CREATE TABLE test_relation_b(trackid INTEGER, trackname TEXT, trackartist INTEGER, FOREIGN KEY(trackartist) REFERENCES test_relation_a(artistid))"
7096+
)
7097+
ds = None
7098+
7099+
ds = gdal.OpenEx(filename, gdal.OF_VECTOR | gdal.OF_UPDATE)
7100+
assert ds.GetRelationshipNames() == [
7101+
"custom_type",
7102+
"test_relation_a_test_relation_b",
7103+
]
7104+
assert ds.GetRelationship("custom_type") is not None
7105+
rel = ds.GetRelationship("test_relation_a_test_relation_b")
7106+
assert rel is not None
7107+
assert rel.GetName() == "test_relation_a_test_relation_b"
7108+
assert rel.GetLeftTableName() == "test_relation_a"
7109+
assert rel.GetRightTableName() == "test_relation_b"
7110+
assert rel.GetCardinality() == gdal.GRC_ONE_TO_MANY
7111+
assert rel.GetType() == gdal.GRT_ASSOCIATION
7112+
assert rel.GetLeftTableFields() == ["artistid"]
7113+
assert rel.GetRightTableFields() == ["trackartist"]
7114+
assert rel.GetRelatedTableType() == "features"
7115+
70897116
ds = None
70907117

70917118

ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp

+14-4
Original file line numberDiff line numberDiff line change
@@ -2018,14 +2018,24 @@ void GDALGeoPackageDataset::ClearCachedRelationships()
20182018

20192019
void GDALGeoPackageDataset::LoadRelationships() const
20202020
{
2021+
m_osMapRelationships.clear();
2022+
2023+
std::vector<std::string> oExcludedTables;
20212024
if (HasGpkgextRelationsTable())
20222025
{
20232026
LoadRelationshipsUsingRelatedTablesExtension();
2027+
2028+
for (const auto &oRelationship : m_osMapRelationships)
2029+
{
2030+
oExcludedTables.emplace_back(
2031+
oRelationship.second->GetMappingTableName());
2032+
}
20242033
}
2025-
else
2026-
{
2027-
LoadRelationshipsFromForeignKeys();
2028-
}
2034+
2035+
// Also load relationships defined using foreign keys (i.e. one-to-many
2036+
// relationships). Here we must exclude any relationships defined from the
2037+
// related tables extension, we don't want them included twice.
2038+
LoadRelationshipsFromForeignKeys(oExcludedTables);
20292039
m_bHasPopulatedRelationships = true;
20302040
}
20312041

ogr/ogrsf_frmts/sqlite/ogrsqlitebase.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ class OGRSQLiteBaseDataSource CPL_NON_FINAL : public GDALPamDataset
216216
OGRErr PragmaCheck(const char *pszPragma, const char *pszExpected,
217217
int nRowsExpected);
218218

219-
void LoadRelationshipsFromForeignKeys() const;
219+
void LoadRelationshipsFromForeignKeys(
220+
const std::vector<std::string> &excludedTables) const;
220221

221222
bool IsSpatialiteLoaded();
222223
static int MakeSpatialiteVersionNumber(int x, int y, int z)

ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp

+30-9
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,10 @@ std::vector<std::string> OGRSQLiteDataSource::GetRelationshipNames(
284284

285285
{
286286
if (!m_bHasPopulatedRelationships)
287-
LoadRelationshipsFromForeignKeys();
287+
{
288+
m_osMapRelationships.clear();
289+
LoadRelationshipsFromForeignKeys({});
290+
}
288291

289292
std::vector<std::string> oasNames;
290293
oasNames.reserve(m_osMapRelationships.size());
@@ -305,7 +308,10 @@ OGRSQLiteDataSource::GetRelationship(const std::string &name) const
305308

306309
{
307310
if (!m_bHasPopulatedRelationships)
308-
LoadRelationshipsFromForeignKeys();
311+
{
312+
m_osMapRelationships.clear();
313+
LoadRelationshipsFromForeignKeys({});
314+
}
309315

310316
auto it = m_osMapRelationships.find(name);
311317
if (it == m_osMapRelationships.end())
@@ -707,15 +713,13 @@ OGRErr OGRSQLiteBaseDataSource::PragmaCheck(const char *pszPragma,
707713
/* LoadRelationshipsFromForeignKeys() */
708714
/************************************************************************/
709715

710-
void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys() const
716+
void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys(
717+
const std::vector<std::string> &excludedTables) const
711718

712719
{
713-
m_osMapRelationships.clear();
714-
715720
if (hDB)
716721
{
717-
auto oResult = SQLQuery(
718-
hDB,
722+
std::string osSQL =
719723
"SELECT m.name, p.id, p.seq, p.\"table\" AS base_table_name, "
720724
"p.\"from\", p.\"to\", "
721725
"p.on_delete FROM sqlite_master m "
@@ -728,8 +732,25 @@ void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys() const
728732
// Same with Spatialite system tables
729733
"AND base_table_name NOT IN ('geometry_columns', "
730734
"'spatial_ref_sys', 'views_geometry_columns', "
731-
"'virts_geometry_columns') "
732-
"ORDER BY m.name");
735+
"'virts_geometry_columns') ";
736+
if (!excludedTables.empty())
737+
{
738+
std::string oExcludedTablesList;
739+
for (const auto &osExcludedTable : excludedTables)
740+
{
741+
oExcludedTablesList += !oExcludedTablesList.empty() ? "," : "";
742+
oExcludedTablesList +=
743+
sqlite3_mprintf("'%q'", osExcludedTable.c_str());
744+
}
745+
746+
osSQL += "AND base_table_name NOT IN (" + oExcludedTablesList +
747+
")"
748+
" AND m.name NOT IN (" +
749+
oExcludedTablesList + ") ";
750+
}
751+
osSQL += "ORDER BY m.name";
752+
753+
auto oResult = SQLQuery(hDB, osSQL.c_str());
733754

734755
if (!oResult)
735756
{

0 commit comments

Comments
 (0)