Skip to content

Commit 48ddaf2

Browse files
authored
Merge pull request #9279 from nyalldawson/gpkg_one_to_many
[gpkg] Also read relationships defined using foreign key constraints
2 parents 18a4515 + 9d207db commit 48ddaf2

File tree

7 files changed

+128
-106
lines changed

7 files changed

+128
-106
lines changed

autotest/ogr/ogr_gpkg.py

+27
Original file line numberDiff line numberDiff line change
@@ -7093,6 +7093,33 @@ def test_ogr_gpkg_relations(tmp_vsimem, tmp_path):
70937093
assert rel.GetRightMappingTableFields() == ["related_id"]
70947094
assert rel.GetRelatedTableType() == "features"
70957095

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

70987125

doc/source/drivers/vector/gpkg.rst

+2-3
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,8 @@ Relationships
168168

169169
.. versionadded:: 3.6
170170

171-
Relationship retrieval is supported, respecting the OGC GeoPackage Related Tables Extension.
172-
If the Related Tables Extension is not in use then relationships will be reported for tables
173-
which utilize FOREIGN KEY constraints.
171+
Many-to-many relationship retrieval is supported, respecting the OGC GeoPackage Related Tables Extension.
172+
One-to-many relationships will also be reported for tables which utilize FOREIGN KEY constraints.
174173

175174
Relationship creation, deletion and updating is supported since GDAL 3.7. Relationships can
176175
only be updated to change their base or related table fields, or the relationship related

ogr/ogrsf_frmts/gpkg/ogr_geopackage.h

+1-7
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ class GDALGeoPackageDataset final : public OGRSQLiteBaseDataSource,
280280
void FixupWrongRTreeTrigger();
281281
void FixupWrongMedataReferenceColumnNameUpdate();
282282
void ClearCachedRelationships();
283-
void LoadRelationships() const;
283+
void LoadRelationships() const override;
284284
void LoadRelationshipsUsingRelatedTablesExtension() const;
285285
static std::string
286286
GenerateNameForRelationship(const char *pszBaseTableName,
@@ -345,12 +345,6 @@ class GDALGeoPackageDataset final : public OGRSQLiteBaseDataSource,
345345
bool AddFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
346346
std::string &failureReason) override;
347347

348-
std::vector<std::string>
349-
GetRelationshipNames(CSLConstList papszOptions = nullptr) const override;
350-
351-
const GDALRelationship *
352-
GetRelationship(const std::string &name) const override;
353-
354348
bool AddRelationship(std::unique_ptr<GDALRelationship> &&relationship,
355349
std::string &failureReason) override;
356350

ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp

+14-43
Original file line numberDiff line numberDiff line change
@@ -2038,14 +2038,24 @@ void GDALGeoPackageDataset::ClearCachedRelationships()
20382038

20392039
void GDALGeoPackageDataset::LoadRelationships() const
20402040
{
2041+
m_osMapRelationships.clear();
2042+
2043+
std::vector<std::string> oExcludedTables;
20412044
if (HasGpkgextRelationsTable())
20422045
{
20432046
LoadRelationshipsUsingRelatedTablesExtension();
2047+
2048+
for (const auto &oRelationship : m_osMapRelationships)
2049+
{
2050+
oExcludedTables.emplace_back(
2051+
oRelationship.second->GetMappingTableName());
2052+
}
20442053
}
2045-
else
2046-
{
2047-
LoadRelationshipsFromForeignKeys();
2048-
}
2054+
2055+
// Also load relationships defined using foreign keys (i.e. one-to-many
2056+
// relationships). Here we must exclude any relationships defined from the
2057+
// related tables extension, we don't want them included twice.
2058+
LoadRelationshipsFromForeignKeys(oExcludedTables);
20492059
m_bHasPopulatedRelationships = true;
20502060
}
20512061

@@ -9813,45 +9823,6 @@ bool GDALGeoPackageDataset::AddFieldDomain(
98139823
return true;
98149824
}
98159825

9816-
/************************************************************************/
9817-
/* GetRelationshipNames() */
9818-
/************************************************************************/
9819-
9820-
std::vector<std::string> GDALGeoPackageDataset::GetRelationshipNames(
9821-
CPL_UNUSED CSLConstList papszOptions) const
9822-
9823-
{
9824-
if (!m_bHasPopulatedRelationships)
9825-
LoadRelationships();
9826-
9827-
std::vector<std::string> oasNames;
9828-
oasNames.reserve(m_osMapRelationships.size());
9829-
for (auto it = m_osMapRelationships.begin();
9830-
it != m_osMapRelationships.end(); ++it)
9831-
{
9832-
oasNames.emplace_back(it->first);
9833-
}
9834-
return oasNames;
9835-
}
9836-
9837-
/************************************************************************/
9838-
/* GetRelationship() */
9839-
/************************************************************************/
9840-
9841-
const GDALRelationship *
9842-
GDALGeoPackageDataset::GetRelationship(const std::string &name) const
9843-
9844-
{
9845-
if (!m_bHasPopulatedRelationships)
9846-
LoadRelationships();
9847-
9848-
auto it = m_osMapRelationships.find(name);
9849-
if (it == m_osMapRelationships.end())
9850-
return nullptr;
9851-
9852-
return it->second.get();
9853-
}
9854-
98559826
/************************************************************************/
98569827
/* AddRelationship() */
98579828
/************************************************************************/

ogr/ogrsf_frmts/sqlite/ogr_sqlite.h

-6
Original file line numberDiff line numberDiff line change
@@ -792,12 +792,6 @@ class OGRSQLiteDataSource final : public OGRSQLiteBaseDataSource
792792
return m_bHaveGeometryColumns;
793793
}
794794

795-
std::vector<std::string>
796-
GetRelationshipNames(CSLConstList papszOptions = nullptr) const override;
797-
798-
const GDALRelationship *
799-
GetRelationship(const std::string &name) const override;
800-
801795
bool AddRelationship(std::unique_ptr<GDALRelationship> &&relationship,
802796
std::string &failureReason) override;
803797
bool ValidateRelationship(const GDALRelationship *poRelationship,

ogr/ogrsf_frmts/sqlite/ogrsqlitebase.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,13 @@ 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+
virtual void LoadRelationships() const;
220+
void LoadRelationshipsFromForeignKeys(
221+
const std::vector<std::string> &excludedTables) const;
222+
std::vector<std::string>
223+
GetRelationshipNames(CSLConstList papszOptions = nullptr) const override;
224+
const GDALRelationship *
225+
GetRelationship(const std::string &name) const override;
220226

221227
bool IsSpatialiteLoaded();
222228
static int MakeSpatialiteVersionNumber(int x, int y, int z)

ogr/ogrsf_frmts/sqlite/ogrsqlitedatasource.cpp

+77-46
Original file line numberDiff line numberDiff line change
@@ -275,45 +275,6 @@ int OGRSQLiteBaseDataSource::GetSpatialiteVersionNumber()
275275
return v;
276276
}
277277

278-
/************************************************************************/
279-
/* GetRelationshipNames() */
280-
/************************************************************************/
281-
282-
std::vector<std::string> OGRSQLiteDataSource::GetRelationshipNames(
283-
CPL_UNUSED CSLConstList papszOptions) const
284-
285-
{
286-
if (!m_bHasPopulatedRelationships)
287-
LoadRelationshipsFromForeignKeys();
288-
289-
std::vector<std::string> oasNames;
290-
oasNames.reserve(m_osMapRelationships.size());
291-
for (auto it = m_osMapRelationships.begin();
292-
it != m_osMapRelationships.end(); ++it)
293-
{
294-
oasNames.emplace_back(it->first);
295-
}
296-
return oasNames;
297-
}
298-
299-
/************************************************************************/
300-
/* GetRelationship() */
301-
/************************************************************************/
302-
303-
const GDALRelationship *
304-
OGRSQLiteDataSource::GetRelationship(const std::string &name) const
305-
306-
{
307-
if (!m_bHasPopulatedRelationships)
308-
LoadRelationshipsFromForeignKeys();
309-
310-
auto it = m_osMapRelationships.find(name);
311-
if (it == m_osMapRelationships.end())
312-
return nullptr;
313-
314-
return it->second.get();
315-
}
316-
317278
/************************************************************************/
318279
/* AddRelationship() */
319280
/************************************************************************/
@@ -704,18 +665,28 @@ OGRErr OGRSQLiteBaseDataSource::PragmaCheck(const char *pszPragma,
704665
}
705666

706667
/************************************************************************/
707-
/* LoadRelationshipsFromForeignKeys() */
668+
/* LoadRelationships() */
708669
/************************************************************************/
709670

710-
void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys() const
671+
void OGRSQLiteBaseDataSource::LoadRelationships() const
711672

712673
{
713674
m_osMapRelationships.clear();
675+
LoadRelationshipsFromForeignKeys({});
676+
m_bHasPopulatedRelationships = true;
677+
}
714678

679+
/************************************************************************/
680+
/* LoadRelationshipsFromForeignKeys() */
681+
/************************************************************************/
682+
683+
void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys(
684+
const std::vector<std::string> &excludedTables) const
685+
686+
{
715687
if (hDB)
716688
{
717-
auto oResult = SQLQuery(
718-
hDB,
689+
std::string osSQL =
719690
"SELECT m.name, p.id, p.seq, p.\"table\" AS base_table_name, "
720691
"p.\"from\", p.\"to\", "
721692
"p.on_delete FROM sqlite_master m "
@@ -728,8 +699,27 @@ void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys() const
728699
// Same with Spatialite system tables
729700
"AND base_table_name NOT IN ('geometry_columns', "
730701
"'spatial_ref_sys', 'views_geometry_columns', "
731-
"'virts_geometry_columns') "
732-
"ORDER BY m.name");
702+
"'virts_geometry_columns') ";
703+
if (!excludedTables.empty())
704+
{
705+
std::string oExcludedTablesList;
706+
for (const auto &osExcludedTable : excludedTables)
707+
{
708+
oExcludedTablesList += !oExcludedTablesList.empty() ? "," : "";
709+
char *pszEscapedName =
710+
sqlite3_mprintf("'%q'", osExcludedTable.c_str());
711+
oExcludedTablesList += pszEscapedName;
712+
sqlite3_free(pszEscapedName);
713+
}
714+
715+
osSQL += "AND base_table_name NOT IN (" + oExcludedTablesList +
716+
")"
717+
" AND m.name NOT IN (" +
718+
oExcludedTablesList + ") ";
719+
}
720+
osSQL += "ORDER BY m.name";
721+
722+
auto oResult = SQLQuery(hDB, osSQL.c_str());
733723

734724
if (!oResult)
735725
{
@@ -806,9 +796,50 @@ void OGRSQLiteBaseDataSource::LoadRelationshipsFromForeignKeys() const
806796
std::move(poRelationship);
807797
}
808798
}
799+
}
800+
}
801+
802+
/************************************************************************/
803+
/* GetRelationshipNames() */
804+
/************************************************************************/
805+
806+
std::vector<std::string> OGRSQLiteBaseDataSource::GetRelationshipNames(
807+
CPL_UNUSED CSLConstList papszOptions) const
808+
809+
{
810+
if (!m_bHasPopulatedRelationships)
811+
{
812+
LoadRelationships();
813+
}
814+
815+
std::vector<std::string> oasNames;
816+
oasNames.reserve(m_osMapRelationships.size());
817+
for (auto it = m_osMapRelationships.begin();
818+
it != m_osMapRelationships.end(); ++it)
819+
{
820+
oasNames.emplace_back(it->first);
821+
}
822+
return oasNames;
823+
}
824+
825+
/************************************************************************/
826+
/* GetRelationship() */
827+
/************************************************************************/
809828

810-
m_bHasPopulatedRelationships = true;
829+
const GDALRelationship *
830+
OGRSQLiteBaseDataSource::GetRelationship(const std::string &name) const
831+
832+
{
833+
if (!m_bHasPopulatedRelationships)
834+
{
835+
LoadRelationships();
811836
}
837+
838+
auto it = m_osMapRelationships.find(name);
839+
if (it == m_osMapRelationships.end())
840+
return nullptr;
841+
842+
return it->second.get();
812843
}
813844

814845
/***********************************************************************/

0 commit comments

Comments
 (0)