diff --git a/scripts-available/CDB_FederatedServerDiagnostics.sql b/scripts-available/CDB_FederatedServerDiagnostics.sql new file mode 100644 index 0000000..459338d --- /dev/null +++ b/scripts-available/CDB_FederatedServerDiagnostics.sql @@ -0,0 +1,145 @@ +-------------------------------------------------------------------------------- +-- Private functions +-------------------------------------------------------------------------------- + +-- +-- Import a foreign table if it does not exist +-- +CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Import_If_Not_Exists(server_internal name, remote_schema name, remote_table name) +RETURNS void +AS $$ +DECLARE + local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema); +BEGIN + IF NOT EXISTS ( + SELECT * FROM pg_class + WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = local_schema) + AND relname = remote_table + ) THEN + EXECUTE format('IMPORT FOREIGN SCHEMA %I LIMIT TO (%I) FROM SERVER %I INTO %I', + remote_schema, remote_table, server_internal, local_schema); + END IF; +END +$$ +LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; + +-- +-- Get the version of a remote PG server +-- +CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_Server_Version_PG(server_internal name) +RETURNS text +AS $$ +DECLARE + remote_schema name := 'pg_catalog'; + remote_table name := 'pg_settings'; + local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema); + remote_server_version text; +BEGIN + PERFORM @extschema@.__CDB_FS_Import_If_Not_Exists(server_internal, remote_schema, remote_table); + + BEGIN + EXECUTE format(' + SELECT setting FROM %I.%I WHERE name = ''server_version''; + ', local_schema, remote_table) INTO remote_server_version; + EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Not enough permissions to access the server "%"', + @extschema@.__CDB_FS_Extract_Server_Name(server_internal); + END; + + RETURN remote_server_version; +END +$$ +LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; + + +-- +-- Get the PostGIS extension version of a remote PG server +-- +CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_PostGIS_Version_PG(server_internal name) +RETURNS text +AS $$ +DECLARE + remote_schema name := 'pg_catalog'; + remote_table name := 'pg_extension'; + local_schema name := @extschema@.__CDB_FS_Create_Schema(server_internal, remote_schema); + remote_postgis_version text; +BEGIN + PERFORM @extschema@.__CDB_FS_Import_If_Not_Exists(server_internal, remote_schema, remote_table); + + BEGIN + EXECUTE format(' + SELECT extversion FROM %I.%I WHERE extname = ''postgis''; + ', local_schema, remote_table) INTO remote_postgis_version; + EXCEPTION WHEN OTHERS THEN + RAISE EXCEPTION 'Not enough permissions to access the server "%"', + @extschema@.__CDB_FS_Extract_Server_Name(server_internal); + END; + + RETURN remote_postgis_version; +END +$$ +LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; + + +-- +-- Get the foreign server options of a remote PG server +-- +CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Foreign_Server_Options_PG(server_internal name) +RETURNS jsonb +AS $$ + -- See https://www.postgresql.org/docs/current/catalog-pg-foreign-server.html + -- See https://www.postgresql.org/docs/current/functions-info.html + SELECT jsonb_object_agg(opt.option_name, opt.option_value) FROM ( + SELECT (pg_options_to_table(srvoptions)).* + FROM pg_foreign_server + WHERE srvname = server_internal + ) AS opt; +$$ +LANGUAGE SQL VOLATILE PARALLEL UNSAFE; + + +-- +-- Collect and return diagnostics info from a remote PG into a jsonb +-- +CREATE OR REPLACE FUNCTION @extschema@.__CDB_FS_Server_Diagnostics_PG(server_internal name) +RETURNS jsonb +AS $$ +DECLARE + remote_server_version text := @extschema@.__CDB_FS_Foreign_Server_Version_PG(server_internal); + remote_postgis_version text := @extschema@.__CDB_FS_Foreign_PostGIS_Version_PG(server_internal); + remote_server_options jsonb := @extschema@.__CDB_FS_Foreign_Server_Options_PG(server_internal); +BEGIN + RETURN jsonb_build_object( + 'server_version', remote_server_version, + 'postgis_version', remote_postgis_version, + 'server_options', remote_server_options + ); +END +$$ +LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; + + + +-------------------------------------------------------------------------------- +-- Public functions +-------------------------------------------------------------------------------- + +-- +-- Collect and return diagnostics info from a remote PG into a jsonb +-- +CREATE OR REPLACE FUNCTION @extschema@.CDB_Federated_Server_Diagnostics(server TEXT) +RETURNS jsonb +AS $$ +DECLARE + server_internal name := @extschema@.__CDB_FS_Generate_Server_Name(input_name => server, check_existence => true); + server_type name := @extschema@.__CDB_FS_server_type(server_internal); +BEGIN + CASE server_type + WHEN 'postgres_fdw' THEN + RETURN @extschema@.__CDB_FS_Server_Diagnostics_PG(server_internal); + ELSE + RAISE EXCEPTION 'Not implemented server type % for remote server %', server_type, server; + END CASE; +END +$$ +LANGUAGE PLPGSQL VOLATILE PARALLEL UNSAFE; diff --git a/scripts-enabled/403-CDB_FederatedServerDiagnostics.sql b/scripts-enabled/403-CDB_FederatedServerDiagnostics.sql new file mode 120000 index 0000000..edab4d4 --- /dev/null +++ b/scripts-enabled/403-CDB_FederatedServerDiagnostics.sql @@ -0,0 +1 @@ +../scripts-available/CDB_FederatedServerDiagnostics.sql \ No newline at end of file diff --git a/test/CDB_FederatedServerDiagnostics.sql b/test/CDB_FederatedServerDiagnostics.sql new file mode 100644 index 0000000..d626004 --- /dev/null +++ b/test/CDB_FederatedServerDiagnostics.sql @@ -0,0 +1,75 @@ +-- =================================================================== +-- create FDW objects +-- =================================================================== +\set QUIET on +SET client_min_messages TO error; +\set VERBOSITY terse +CREATE EXTENSION postgres_fdw; + +CREATE ROLE cdb_fs_tester LOGIN PASSWORD 'cdb_fs_passwd'; +GRANT CONNECT ON DATABASE contrib_regression TO cdb_fs_tester; + +-- Create database to be used as remote +CREATE DATABASE cdb_fs_tester OWNER cdb_fs_tester; + +SELECT 'C1', cartodb.CDB_Federated_Server_Register_PG(server => 'loopback'::text, config => '{ + "server": { + "host": "localhost", + "port": @@PGPORT@@ + }, + "credentials": { + "username": "cdb_fs_tester", + "password": "cdb_fs_passwd" + } +}'::jsonb); + +\c cdb_fs_tester postgres +CREATE EXTENSION postgis; +\c contrib_regression postgres +\set QUIET off + + +-- =================================================================== +-- Test server diagnostics function(s) +-- =================================================================== +\echo '%% It raises an error if the server does not exist' +SELECT '1.1', cartodb.CDB_Federated_Server_Diagnostics(server => 'doesNotExist'); + +\echo '%% It returns a jsonb object' +SELECT '1.2', pg_typeof(cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback')); + +\echo '%% It returns the server version' +SELECT '1.3', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> format('{"server_version": "%s"}', setting)::jsonb + FROM pg_settings WHERE name = 'server_version'; + +\echo '%% It returns the postgis version' +SELECT '1.4', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> format('{"postgis_version": "%s"}', extversion)::jsonb + FROM pg_extension WHERE extname = 'postgis'; + +\echo '%% It returns null as the postgis version if it is not installed' +\set QUIET on +\c cdb_fs_tester postgres +DROP EXTENSION postgis; +\c contrib_regression postgres +\set QUIET off +SELECT '1.5', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> '{"postgis_version": null}'::jsonb; + +\echo '%% It returns the remote server options' +SELECT '1.6', cartodb.CDB_Federated_Server_Diagnostics(server => 'loopback') @> '{"server_options": {"host": "localhost", "port": "@@PGPORT@@", "updatable": "false", "extensions": "postgis", "fetch_size": "1000", "use_remote_estimate": "true"}}'::jsonb; + + +-- =================================================================== +-- Cleanup +-- =================================================================== +\set QUIET on +SELECT 'D1', cartodb.CDB_Federated_Server_Unregister(server => 'loopback'::text); +-- Reconnect, using a new session in order to close FDW connections +\connect +DROP DATABASE cdb_fs_tester; + +-- Drop role +REVOKE CONNECT ON DATABASE contrib_regression FROM cdb_fs_tester; +DROP ROLE cdb_fs_tester; + +DROP EXTENSION postgres_fdw; +\set QUIET off diff --git a/test/CDB_FederatedServerDiagnostics_expect b/test/CDB_FederatedServerDiagnostics_expect new file mode 100644 index 0000000..2f1f7ae --- /dev/null +++ b/test/CDB_FederatedServerDiagnostics_expect @@ -0,0 +1,14 @@ +C1| +%% It raises an error if the server does not exist +ERROR: Server "doesNotExist" does not exist +%% It returns a jsonb object +1.2|jsonb +%% It returns the server version +1.3|t +%% It returns the postgis version +1.4|t +%% It returns null as the postgis version if it is not installed +1.5|t +%% It returns the remote server options +1.6|t +D1|