Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ogr2ogr: transfer relationships (when possible) from source dataset to target dataset #11411

Merged
merged 3 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions apps/ogr2ogr_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2217,6 +2217,88 @@ GDALVectorTranslateCreateCopy(GDALDriver *poDriver, const char *pszDest,
return poOut;
}

/************************************************************************/
/* CopyRelationships() */
/************************************************************************/

static void CopyRelationships(GDALDataset *poODS, GDALDataset *poDS)
{
if (!poODS->GetDriver()->GetMetadataItem(GDAL_DCAP_CREATE_RELATIONSHIP))
return;

const auto aosRelationshipNames = poDS->GetRelationshipNames();
if (aosRelationshipNames.empty())
return;

// Collect target layer names
std::set<std::string> oSetDestLayerNames;
for (const auto &poLayer : poDS->GetLayers())
{
oSetDestLayerNames.insert(poLayer->GetName());
}

// Iterate over all source relationships
for (const auto &osRelationshipName : aosRelationshipNames)
{
const auto poSrcRelationship =
poDS->GetRelationship(osRelationshipName);
if (!poSrcRelationship)
continue;

// Skip existing relationship of the same name
if (poODS->GetRelationship(osRelationshipName))
continue;

bool canAdd = true;
const auto &osLeftTableName = poSrcRelationship->GetLeftTableName();
if (!osLeftTableName.empty() &&
!cpl::contains(oSetDestLayerNames, osLeftTableName))
{
CPLDebug("GDALVectorTranslate",
"Skipping relationship %s because its left table (%s) "
"does not exist in target dataset",
osRelationshipName.c_str(), osLeftTableName.c_str());
canAdd = false;
}

const auto &osRightTableName = poSrcRelationship->GetRightTableName();
if (!osRightTableName.empty() &&
!cpl::contains(oSetDestLayerNames, osRightTableName))
{
CPLDebug("GDALVectorTranslate",
"Skipping relationship %s because its right table (%s) "
"does not exist in target dataset",
osRelationshipName.c_str(), osRightTableName.c_str());
canAdd = false;
}

const auto &osMappingTableName =
poSrcRelationship->GetMappingTableName();
if (!osMappingTableName.empty() &&
!cpl::contains(oSetDestLayerNames, osMappingTableName))
{
CPLDebug("GDALVectorTranslate",
"Skipping relationship %s because its mapping table (%s) "
"does not exist in target dataset",
osRelationshipName.c_str(), osMappingTableName.c_str());
canAdd = false;
}

if (canAdd)
{
std::string osFailureReason;
if (!poODS->AddRelationship(
std::make_unique<GDALRelationship>(*poSrcRelationship),
osFailureReason))
{
CPLDebug("GDALVectorTranslate",
"Cannot add relationship %s: %s",
osRelationshipName.c_str(), osFailureReason.c_str());
}
}
}
}

/************************************************************************/
/* GDALVectorTranslate() */
/************************************************************************/
Expand Down Expand Up @@ -3590,6 +3672,9 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS,
GDALDestroyScaledProgress(pProgressArg);
}
}

CopyRelationships(poODS, poDS);

/* -------------------------------------------------------------------- */
/* Process DS style table */
/* -------------------------------------------------------------------- */
Expand Down
Binary file not shown.
73 changes: 73 additions & 0 deletions autotest/utilities/test_ogr2ogr_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -3008,3 +3008,76 @@ def my_handler(errorClass, errno, msg):
"2022/05/31 12:34:56",
"2022/05/31 12:34:56+1230",
]


###############################################################################


@gdaltest.enable_exceptions()
@pytest.mark.require_driver("GPKG")
def test_ogr2ogr_lib_transfer_gpkg_relationships(tmp_vsimem):

out_filename = str(tmp_vsimem / "relationships.gpkg")
gdal.VectorTranslate(out_filename, "../ogr/data/gpkg/relation_mapping_table.gpkg")

with gdal.OpenEx(out_filename) as ds:
assert ds.GetRelationshipNames() == ["a_b_attributes"]
relationship = ds.GetRelationship("a_b_attributes")
assert relationship.GetLeftTableName() == "a"
assert relationship.GetRightTableName() == "b"
assert relationship.GetMappingTableName() == "my_mapping_table"

gdal.VectorTranslate(
out_filename,
"../ogr/data/gpkg/relation_mapping_table.gpkg",
layers=["a", "my_mapping_table"],
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRelationshipNames() is None

gdal.VectorTranslate(
out_filename,
"../ogr/data/gpkg/relation_mapping_table.gpkg",
layers=["b", "my_mapping_table"],
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRelationshipNames() is None

gdal.VectorTranslate(
out_filename, "../ogr/data/gpkg/relation_mapping_table.gpkg", layers=["a", "b"]
)
with gdal.OpenEx(out_filename) as ds:
assert ds.GetRelationshipNames() is None


###############################################################################


@gdaltest.enable_exceptions()
@pytest.mark.require_driver("OpenFileGDB")
def test_ogr2ogr_lib_transfer_filegdb_relationships(tmp_vsimem):

out_filename = str(tmp_vsimem / "relationships.gdb")
gdal.VectorTranslate(
out_filename, "../ogr/data/filegdb/relationships.gdb", format="OpenFileGDB"
)

with gdal.OpenEx(out_filename) as ds:
assert set(ds.GetRelationshipNames()) == set(
[
"composite_many_to_many",
"composite_one_to_one",
"points__ATTACHREL",
"simple_attributed",
"simple_backward_message_direction",
"simple_both_message_direction",
"simple_forward_message_direction",
"simple_many_to_many",
"simple_one_to_many",
"simple_relationship_one_to_one",
]
)
relationship = ds.GetRelationship("composite_many_to_many")
assert relationship.GetLeftTableName() == "table6"
assert relationship.GetRightTableName() == "table7"
assert relationship.GetMappingTableName() == "composite_many_to_many"
10 changes: 7 additions & 3 deletions ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1679,8 +1679,10 @@ int GDALGeoPackageDataset::Open(GDALOpenInfo *poOpenInfo,
"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)";
"lower(table_name) FROM gpkg_contents) AND "
"EXISTS (SELECT 1 FROM sqlite_master WHERE "
"type IN ('table', 'view') AND "
"lower(name) = lower(mapping_table_name))";
}
if (EQUAL(pszListAllTables, "YES") ||
(!bHasASpatialOrAttributes && EQUAL(pszListAllTables, "AUTO")))
Expand Down Expand Up @@ -2185,7 +2187,9 @@ void GDALGeoPackageDataset::LoadRelationshipsUsingRelatedTablesExtension() const
const int nMappingTableCount = SQLGetInteger(hDB, pszSQL, nullptr);
sqlite3_free(pszSQL);

if (nMappingTableCount < 1)
if (nMappingTableCount < 1 &&
!const_cast<GDALGeoPackageDataset *>(this)->GetLayerByName(
pszMappingTableName))
{
CPLError(CE_Warning, CPLE_AppDefined,
"Relationship mapping table %s does not exist",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1881,6 +1881,9 @@ bool OGROpenFileGDBDataSource::AddRelationship(
std::unique_ptr<GDALRelationship> &&relationship,
std::string &failureReason)
{
if (FlushCache(false) != CE_None)
return false;

const std::string relationshipName(relationship->GetName());
if (eAccess != GA_Update)
{
Expand Down
Loading