diff --git a/autotest/ogr/ogr_pg.py b/autotest/ogr/ogr_pg.py index 37d03c4a95b0..8a8ad9a75357 100755 --- a/autotest/ogr/ogr_pg.py +++ b/autotest/ogr/ogr_pg.py @@ -6067,3 +6067,57 @@ def test_ogr_pg_no_postgis_GEOMETRY_NAME(pg_ds): gdal.GetLastErrorMsg() == "GEOMETRY_NAME=foo ignored, and set instead to 'wkb_geometry' as it is the only geometry column name recognized for non-PostGIS enabled databases." ) + + +############################################################################### +# Test ignored conflicts + + +@only_without_postgis +def test_ogr_pg_skip_conflicts(pg_ds): + pg_ds.ExecuteSQL( + "CREATE TABLE test_ogr_skip_conflicts(id SERIAL PRIMARY KEY, gml_id character(16), beginnt character(20), UNIQUE(gml_id, beginnt))" + ) + + with gdal.config_option("OGR_PG_SKIP_CONFLICTS", "YES"): + # OGR_PG_SKIP_CONFLICTS and OGR_PG_RETRIEVE_FID cannot be used at the same time + with gdal.config_option("OGR_PG_RETRIEVE_FID", "YES"): + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.GetLayerByName("test_ogr_skip_conflicts") + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG3" + feat["beginnt"] = "2020-07-10T04:48:14Z" + with gdal.quiet_errors(): + assert lyr.CreateFeature(feat) != ogr.OGRERR_NONE + + with gdal.config_option("OGR_PG_RETRIEVE_FID", "NO"): + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.GetLayerByName("test_ogr_skip_conflicts") + + assert lyr.GetFeatureCount() == 0 + + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG3" + feat["beginnt"] = "2020-07-10T04:48:14Z" + assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE + assert lyr.GetFeatureCount() == 1 + + # Insert w/o OGR_PG_SKIP_CONFLICTS=YES succeeds, but doesn't add a feature + with gdal.config_option("OGR_PG_SKIP_CONFLICTS", "YES"): + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.GetLayerByName("test_ogr_skip_conflicts") + + assert lyr.GetFeatureCount() == 1 + + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG3" + feat["beginnt"] = "2020-07-10T04:48:14Z" + assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE + assert lyr.GetFeatureCount() == 1 + + # Other feature succeeds and increments the feature count + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG4" + feat["beginnt"] = "2020-07-10T04:48:14Z" + assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE + assert lyr.GetFeatureCount() == 2 diff --git a/doc/source/drivers/vector/nas.rst b/doc/source/drivers/vector/nas.rst index af04a7c74d3c..e64f09b10a5e 100644 --- a/doc/source/drivers/vector/nas.rst +++ b/doc/source/drivers/vector/nas.rst @@ -45,6 +45,10 @@ population - which was default in ALKIS-Import. The information found there was redundant to the relation fields also contained in original elements/tables. Enabling the option also made progress reporting available. +Duplicate data in datasets will usually causes errors. When importing separate +datasets into PostgreSQL it is useful to enable "OGR_PG_SKIP_CONFLICTS" to skip +conflicting features. + This driver was implemented within the context of the `PostNAS project `__, which has more information on its use and other related projects. diff --git a/doc/source/drivers/vector/pg.rst b/doc/source/drivers/vector/pg.rst index 7c709f76e475..67271faf1052 100644 --- a/doc/source/drivers/vector/pg.rst +++ b/doc/source/drivers/vector/pg.rst @@ -464,6 +464,16 @@ The following configuration options are available: create) the ``ogr_system_tables.metadata`` table to retrieve and store layer metadata. +- .. config:: OGR_PG_SKIP_CONFLICTS + :choices: YES, NO + :default: NO + + If set to "YES" (not the default), + conflicts when inserting features will be skipped + (requires OGR_PG_RETRIEVE_FID to be off and only applies when PG_USE_COPY + is off). + + Examples ~~~~~~~~ diff --git a/ogr/ogrsf_frmts/pg/ogr_pg.h b/ogr/ogrsf_frmts/pg/ogr_pg.h index 393afb5f9dec..213f4e9665d3 100644 --- a/ogr/ogrsf_frmts/pg/ogr_pg.h +++ b/ogr/ogrsf_frmts/pg/ogr_pg.h @@ -339,6 +339,7 @@ class OGRPGTableLayer final : public OGRPGLayer void CheckGeomTypeCompatibility(int iGeomField, OGRGeometry *poGeom); int bRetrieveFID = true; + int bSkipConflicts = false; int bHasWarnedAlreadySetFID = false; char **papszOverrideColumnTypes = nullptr; diff --git a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp index f46f8b393c8e..27883d8877b6 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp @@ -172,7 +172,9 @@ OGRPGTableLayer::OGRPGTableLayer(OGRPGDataSource *poDSIn, // Just in provision for people yelling about broken backward // compatibility. bRetrieveFID( - CPLTestBool(CPLGetConfigOption("OGR_PG_RETRIEVE_FID", "TRUE"))) + CPLTestBool(CPLGetConfigOption("OGR_PG_RETRIEVE_FID", "TRUE"))), + bSkipConflicts( + CPLTestBool(CPLGetConfigOption("OGR_PG_SKIP_CONFLICTS", "FALSE"))) { poDS = poDSIn; pszQueryStatement = nullptr; @@ -2069,10 +2071,19 @@ OGRErr OGRPGTableLayer::CreateFeatureViaInsert(OGRFeature *poFeature) if (bRetrieveFID && pszFIDColumn != nullptr && poFeature->GetFID() == OGRNullFID) { + if (bSkipConflicts) + { + CPLError(CE_Failure, CPLE_AppDefined, + "fid retrieval and skipping conflicts are not supported " + "at the same time."); + return OGRERR_FAILURE; + } bReturnRequested = TRUE; osCommand += " RETURNING "; osCommand += OGRPGEscapeColumnName(pszFIDColumn); } + else if (bSkipConflicts) + osCommand += " ON CONFLICT DO NOTHING"; /* -------------------------------------------------------------------- */ /* Execute the insert. */