Function to create Foreign Server, does not create foreign server - postgresql

I have created the function below to create a Foreign Server. The function runs without error, but nothing gets created. This is not a rights issue as I can run the individual commands just fine and the server gets created.
CREATE OR REPLACE FUNCTION sch.helper_create_linked_server(servername text, host text, port text, dbname text, localuser text, remoteuser text, password text, timeout text)
RETURNS void
LANGUAGE plpgsql
AS $function$
declare srv_name text;
tmp text;
begin
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE EXTENSION IF NOT EXISTS dblink;
execute format($s$ select srvname from pg_foreign_server where srvname = %L $s$, servername) into srv_name;
if srv_name is null then
execute format($ex$
CREATE SERVER %1$I
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host %2$L, port %3$L, dbname %4$L, connect_timeout %5$L);
$ex$, servername, host, port, dbname, timeout, remoteuser);
raise info 'server created: %', servername;
execute format($ex$
create user mapping for %I
server %I
options (user %L, password %L)
$ex$, localuser, servername, remoteuser, password);
raise info '%', 'umapping created';
perform format($$ ALTER SERVER %1$I OWNER TO %2$I $$, servername, remoteuser);
raise info '%', 'assigned user to server';
end if;
end $function$
I have printed out the command that gets generated and it is correct. This is how I call the function:
perform sch.helper_create_linked_server(link_name, conn_props->>'endpoint', conn_props->>'port', conn_props->>'db', conn_props->>'user', conn_props->>'user', rs_pwd, conn_props->>'timeout');
Also, all the raise info messages are printed as well, so the code is running. And no, the server is not already created.
I cannot see why this will not create.
Any advice appreciated.
Thanks!

Related

How to create a function in PostgreSQL like SQL Server BACKUP DATABASE TO DISK

I'm trying without success to create a function in Postgres that save a table or database taking one or two parameters. In this case I was trying to create it with only one parameter(name of the table or database) and backup this table/db
--SELECT backup_table(sports)
CREATE FUNCTION backup_table(TEXT) RETURNS BOOLEAN AS
$$
DECLARE
table_x ALIAS FOR $1;
BEGIN
COPY table_x FROM 'C:/path/backup_db' WITH (FORMAT CSV);
RAISE NOTICE 'Saved correctly the table %',$1;
RETURN BOOLEAN;
END;
$$ LANGUAGE plpgsql;
I've always receive the error when I try to execute the function SELECT backup_table(sports):
"The column sports doesnt exists."
SQL state: 42703
Character: 21
The idea is to create the function like the equivalent of SQL Server BACKUP DATABASE TO DISK, or equivalent to pg_dump command
pg_dump -U -W -F t sports > C:/path/backup_db;
I know about SQL but now I'm just stuck with this error.

why can I not parse variable parsed from outside?

I am trying to make a drop database script, which I would have to trigger using psql.
psql ... -f reset-database.sql -v dbname=$database
The problem is that I am not able to access the variable :dbname in my script.
DO
$$
BEGIN
IF EXISTS (SELECT EXISTS( SELECT datname FROM pg_catalog.pg_database WHERE datname = :dbname)) THEN
UPDATE pg_database SET datallowconn = 'false' WHERE datname = :dbname;
SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = :dbname;
DROP DATABASE IF EXISTS :dbname;
END IF;
END
$$
This is executed as is, and :dbname is not replaced with the database name parsed in as as variable? Why? And how do I parse it?
The variable substitution will not work in a DO statement, because the statement body is a (dollar quoted) string literal. Otherwise, it should work fine, but you have to use single quotes in your metadata query. Besides, you cannot run DROP DATABASE inside a DO statement, since you cannot run it inside a transaction.
Also, don't update catalog tables.
You can use psql's \if for conditional processing:
SELECT EXISTS (
SELECT 1 FROM pg_catalog.pg_database
WHERE datname = :'dbname'
) AS have_db \gset
\if :have_db
ALTER DATABASE :dbname ALLOW_CONNECTIONS FALSE;
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = :'dbname';
DROP DATABASE :dbname;
\endif
From PostgreSQL v13 on, this is much simpler:
DROP DATABASE IF EXISTS :dbname WITH (FORCE);

How to insert stored functions and triggers in a Postgres container

I have a containerized Postgres database and I am trying to create a function and a trigger: a simple notification on a new row insertion.
It may seem stupid but I am unable to find how to write the code, where should I push it to the Postgres container, and how to programmatically insert the code inside the container.
I can see the following file structure
Database/Schemas/public/
|---tables
| |___requests (tablename)
| |__Triggers
|
|___Functions
I only (vaguely) found a way to copy the code inside when using the /docker-entrypoint-initdb.d/ folder (a COPY in the Dockerfile).
This injection works only - as per the Docker/Postgres documentation - when a database is created. This means I have to drop and recreate the database. This is wrong as I can't make any change to a production database.
Furthermore, when I adjoin the other SQL commands to the CREATE TABLE command in the .sql file, then the order of execution of these commands leads to errors (unknown table...).
My init.sqlfile programmatically copied into the /docker-entrypoint-initdb.d/ folder is:
-- init_table.sql
CREATE TABLE IF NOT EXISTS public.requests (
id serial PRIMARY KEY,
...,
);
-- create_function_notify.sql
CREATE OR REPLACE FUNCTION notify_insert() RETURNS trigger AS
$$
BEGIN
PERFORM pg_notify('new_notif', row_to_json(NEW)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- create_trigger_new_event.sql
CREATE TRIGGER new_event
AFTER INSERT
ON public.requests
FOR EACH ROW
EXECUTE PROCEDURE notify_insert();
LANGUAGE plpgsql;
I also tried a "sh" version (dixit PG docs) without success: it throws an error around "$$".
#/bin/sh
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE OR REPLACE FUNCTION notify_insert() RETURNS trigger AS
$$
LANGUAGE plpgsql;
EOSQL
A Dockerfile instruction COPY ./pg_notify.sql /docker-entrypoint-initdb.d/ sets up the desired trigger and notification related function to the containerized Postgres database (on creation only).
Following Bergi's remark, the correct code is:
-- pg_notify.sql
CREATE TABLE IF NOT EXISTS public.requests (
id serial PRIMARY KEY,
app varchar,
url varchar,
ip varchar,
host varchar,
req_at VARCHAR,
d BIGINT
);
CREATE OR REPLACE FUNCTION notify_insert() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
PERFORM pg_notify('new_notification', row_to_json(NEW)::text);
RETURN NEW;
END;
$$;
CREATE TRIGGER new_event
AFTER INSERT
ON requests
FOR EACH ROW
EXECUTE PROCEDURE notify_insert();

PostgreSQL: Assign generated password in CREATE ROLE

I'm trying to setup a DB initialization script. I need to avoid manual configuration steps AND hardcoded passwords in the source code. I have a function which generates random passwords (adapted from https://www.postgresql.org/message-id/46685BBD.3070809#hagander.net).
The function works (e.g. select generate_random_password()), but I cannot get the "CREATE ROLE" command to accept anything except raw strings.
Does anyone know how I can set the password with the return value of a function that's already been declared? Do I need to mess around with EVALUATE (like a shell-script using eval)?
This:
CREATE ROLE my_user WITH LOGIN CREATEDB PASSWORD generate_random_password();
Yields: SQL Error [42601]: ERROR: syntax error at or near "generate_random_password"
This:
DO $$
DECLARE
passwd varchar := generate_random_password();
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user') THEN
CREATE ROLE my_user WITH LOGIN CREATEDB PASSWORD passwd;
END IF;
END
$$;
Yields: SQL Error [42601]: ERROR: syntax error at or near "passwd" Position: 208
You will need dynamic SQL:
DO $$
DECLARE
passwd varchar := generate_random_password();
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'my_user') THEN
EXECUTE FORMAT(
'CREATE ROLE my_user WITH LOGIN CREATEDB PASSWORD %L', passwd
);
END IF;
END
$$;

Using pg_dump with no owner information still outputting role information

I dumped a database using pg_dump -O -x expecting all ownership and role information to be ignored, however, it is still including specific mentions to manager roles in the original database which are failing to be imported because they don't exist in the new database which I'm importing into. See the snippet of the dump.sql file:
--
-- Name: reassign_owned(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION reassign_owned() RETURNS event_trigger
LANGUAGE plpgsql
AS $$
begin
-- do not execute if member of rds_superuser
IF EXISTS (select 1 from pg_catalog.pg_roles where rolname = 'rds_superuser')
AND pg_has_role(current_user, 'rds_superuser', 'member') THEN
RETURN;
END IF;
-- do not execute if not member of manager role
IF NOT pg_has_role(current_user, 'rdsbroker_xxxxx_manager', 'member') THEN
RETURN;
END IF;
-- do not execute if superuser
IF EXISTS (SELECT 1 FROM pg_user WHERE usename = current_user and usesuper = true) THEN
RETURN;
END IF;
EXECUTE 'reassign owned by "' || current_user || '" to "rdsbroker_xxxxx_manager"';
end
$$;
-O means that pg_dump itself does not emit ownership information. It doesn't cause it to edit the source code of any functions it might dump, in an attempt to prevent those functions from doing what they were written to do.
I've done a quick search in pg_dump and pg_restore source code and I see no usage of REASSIGN OWNED statement.