diff --git a/autotest/ogr/ogr_pg.py b/autotest/ogr/ogr_pg.py index 65e1ac68ffaa..97fec3fedafa 100755 --- a/autotest/ogr/ogr_pg.py +++ b/autotest/ogr/ogr_pg.py @@ -4849,6 +4849,13 @@ def test_ogr_pg_84(pg_ds): def test_ogr_pg_metadata(pg_ds, run_number): pg_ds = reconnect(pg_ds, update=1) + + if run_number == 1: + pg_ds.ExecuteSQL( + "DROP EVENT TRIGGER IF EXISTS ogr_system_tables_event_trigger_for_metadata" + ) + pg_ds.ExecuteSQL("DROP SCHEMA ogr_system_tables CASCADE") + pg_ds.StartTransaction() lyr = pg_ds.CreateLayer( "test_ogr_pg_metadata", geom_type=ogr.wkbPoint, options=["OVERWRITE=YES"] @@ -4903,7 +4910,6 @@ def test_ogr_pg_metadata_restricted_user(pg_ds): pg_ds = reconnect(pg_ds, update=1) try: - pg_ds.ExecuteSQL("CREATE ROLE test_ogr_pg_metadata_restricted_user") with pg_ds.ExecuteSQL("SELECT current_schema()") as lyr: f = lyr.GetNextFeature() @@ -4923,6 +4929,7 @@ def test_ogr_pg_metadata_restricted_user(pg_ds): ) pg_ds = reconnect(pg_ds, update=1) + pg_ds.ExecuteSQL("DROP SCHEMA ogr_system_tables CASCADE") pg_ds.ExecuteSQL("SET ROLE test_ogr_pg_metadata_restricted_user") lyr = pg_ds.CreateLayer( @@ -4933,9 +4940,12 @@ def test_ogr_pg_metadata_restricted_user(pg_ds): with gdal.quiet_errors(): lyr.SetMetadata({"foo": "bar"}) - gdal.ErrorReset() - pg_ds = reconnect(pg_ds, update=1) - assert gdal.GetLastErrorMsg() == "" + gdal.ErrorReset() + pg_ds = reconnect(pg_ds, update=1) + assert ( + gdal.GetLastErrorMsg() + == "User lacks super user privilege to be able to create event trigger ogr_system_tables_event_trigger_for_metadata" + ) finally: pg_ds = reconnect(pg_ds, update=1) diff --git a/doc/source/drivers/vector/pg.rst b/doc/source/drivers/vector/pg.rst index f1f4bac2d085..b6857c97b4a1 100644 --- a/doc/source/drivers/vector/pg.rst +++ b/doc/source/drivers/vector/pg.rst @@ -460,9 +460,13 @@ The following configuration options are available: :default: YES :since: 3.9 - If set to "YES" (the default), the driver will try to use (and potentially - create) the ``ogr_system_tables.metadata`` table to retrieve and store - layer metadata. + If set to "YES" (the default), the driver will try to use the + ``ogr_system_tables.metadata`` table to retrieve and store + layer metadata. To be able to store metadata, the schema ``ogr_system_tables`` + and the event trigger ``ogr_system_tables_event_trigger_for_metadata`` must + already exist in the database, or the current user must have sufficient rights + (super-user rights for the event trigger) to be able to create them. If not, + a warning will be raised. - .. config:: OGR_PG_SKIP_CONFLICTS :choices: YES, NO diff --git a/ogr/ogrsf_frmts/pg/ogr_pg.h b/ogr/ogrsf_frmts/pg/ogr_pg.h index 0a9231bf9d07..626f7b1a60a6 100644 --- a/ogr/ogrsf_frmts/pg/ogr_pg.h +++ b/ogr/ogrsf_frmts/pg/ogr_pg.h @@ -657,6 +657,9 @@ class OGRPGDataSource final : public GDALDataset std::optional FindSchema(const char *pszSchemaNameIn); + bool IsSuperUser(); + bool OGRSystemTablesEventTriggerExists(); + public: PGver sPostgreSQLVersion = {0, 0, 0}; PGver sPostGISVersion = {0, 0, 0}; diff --git a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp index b7341955c48c..9361efa26761 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp @@ -3153,6 +3153,16 @@ bool OGRPGDataSource::CreateMetadataTableIfNeeded() m_bCreateMetadataTableIfNeededRun = true; + const bool bIsSuperUser = IsSuperUser(); + if (!bIsSuperUser && !OGRSystemTablesEventTriggerExists()) + { + CPLError(CE_Warning, CPLE_AppDefined, + "User lacks super user privilege to be able to create event " + "trigger ogr_system_tables_event_trigger_for_metadata"); + m_bCreateMetadataTableIfNeededSuccess = true; + return true; + } + PGresult *hResult; hResult = OGRPG_PQexec( @@ -3266,30 +3276,33 @@ bool OGRPGDataSource::CreateMetadataTableIfNeeded() } OGRPGClearResult(hResult); - hResult = - OGRPG_PQexec(hPGConn, "DROP EVENT TRIGGER IF EXISTS " - "ogr_system_tables_event_trigger_for_metadata"); - if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && - PQresultStatus(hResult) != PGRES_TUPLES_OK)) + if (bIsSuperUser) { + hResult = OGRPG_PQexec(hPGConn, + "DROP EVENT TRIGGER IF EXISTS " + "ogr_system_tables_event_trigger_for_metadata"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); - return false; - } - OGRPGClearResult(hResult); - hResult = OGRPG_PQexec( - hPGConn, - "CREATE EVENT TRIGGER ogr_system_tables_event_trigger_for_metadata " - "ON sql_drop " - "EXECUTE FUNCTION " - "ogr_system_tables.event_trigger_function_for_metadata()"); - if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && - PQresultStatus(hResult) != PGRES_TUPLES_OK)) - { + hResult = OGRPG_PQexec( + hPGConn, + "CREATE EVENT TRIGGER ogr_system_tables_event_trigger_for_metadata " + "ON sql_drop " + "EXECUTE FUNCTION " + "ogr_system_tables.event_trigger_function_for_metadata()"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); - return false; } - OGRPGClearResult(hResult); m_bCreateMetadataTableIfNeededSuccess = true; m_bOgrSystemTablesMetadataTableExistenceTested = true; @@ -3297,6 +3310,35 @@ bool OGRPGDataSource::CreateMetadataTableIfNeeded() return true; } +/************************************************************************/ +/* IsSuperUser() */ +/************************************************************************/ + +bool OGRPGDataSource::IsSuperUser() +{ + PGresult *hResult = OGRPG_PQexec( + hPGConn, "SELECT usesuper FROM pg_user WHERE usename = CURRENT_USER"); + const bool bRet = + (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0) && + strcmp(PQgetvalue(hResult, 0, 0), "t") == 0); + OGRPGClearResult(hResult); + return bRet; +} + +/************************************************************************/ +/* OGRSystemTablesEventTriggerExists() */ +/************************************************************************/ + +bool OGRPGDataSource::OGRSystemTablesEventTriggerExists() +{ + PGresult *hResult = + OGRPG_PQexec(hPGConn, "SELECT 1 FROM pg_event_trigger WHERE evtname = " + "'ogr_system_tables_event_trigger_for_metadata'"); + const bool bRet = (hResult && PQntuples(hResult) == 1); + OGRPGClearResult(hResult); + return bRet; +} + /************************************************************************/ /* HasOgrSystemTablesMetadataTable() */ /************************************************************************/