I almost never use superuser. In rare cases that I use a function that executes the command with SECURITY DEFINER permissions. However, a subscriber cannot be executed inside a function. What is a workaround for this without create_slot= false?
CREATE OR REPLACE FUNCTION public.exec_su(text)
RETURNS void
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
BEGIN
EXECUTE $1;
END
$BODY$;
select public.exec_su('CREATE SUBSCRIPTION n1_subscription CONNECTION 'host=pg12x_aaa port=5432 user=rep password=aaa dbname=aaa' PUBLICATION n1_publication;');
Those are the two work arounds. Either do it outside of a function (and outside of a transaction block), or do it with create_slot=false. There are no extra choices.
Related
I have many stored procedure in my postgresql db,
and for some reason i need to run many procedure in transaction so if there is a error it will rollback.
is there any way to do this?
edit 1
i run this through java and for some reason i cant make transaction from java and i cant run query string, just store procedure only.
I actually thinking making procedure like this
CREATE OR REPLACE FUNCTION ldt_pricing_rule_v1_api.start()
RETURNS VOID
LANGUAGE PLPGSQL
SECURITY DEFINER
AS $$
BEGIN
EXECUTE 'begin transaction'
RETURN;
END
$$;
select ldt_pricing_rule_v1_api.start();
but it's will display this
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
BEGIN ... COMMIT should to work.
BEGIN
SELECT func1();
SELECT func2();
COMMIT;
PostgreSQL 11 (it is not released yet) has procedures where you can control transactions explicitly. Procedures are started by CALL statement like any other databases. Now, PostgreSQL functions doesn't allow control transactions (explicitly).
Any PostgreSQL function is executed under transaction - explicitly started by user (like my example), or implicitly started by system (by autocommit mode).
So outer BEGIN starts explicit transaction:
BEGIN
SELECT func1();
SELECT func2();
COMMIT;
and if there is any unhandled fail, then only ROLLBACK command is available.
or implicit transaction:
CREATE OR REPLACE FUNCTION outerfx()
RETURNS void AS $$
BEGIN
PERFORM func1();
PERFORM func2();
END;
$$ LANGUAGE plpgsql;
SELECT outerfx(); -- starts outer transaction implicitly.
Now, functions func1, func2 are executed under transaction too.
Say we have two functions. First should check permissions and if all goes right update table. Here it is:
CREATE OR REPLACE FUNCTION public.clients_test(
_clientid int
,_comments varchar
)
RETURNS void AS
$BODY$
declare _result varchar;
BEGIN
if now()::time>'17:00'::time then
select public.clients_check_17_00() into _result;
end if;
update clients set comments=_comments where clientid=_clientid;
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 1;
ALTER FUNCTION public.clients_test(int, varchar) OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_test(int, varchar) TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_test(int, varchar) TO "RestrictedAccess";
REVOKE ALL ON FUNCTION public.clients_test(int, varchar) FROM public;
Second function doing nothing with database and exists only for security reasons. I was going to call it from the fist one. Here it is:
CREATE OR REPLACE FUNCTION public.clients_check_17_00()
RETURNS void AS
$BODY$
BEGIN
END
$BODY$
LANGUAGE plpgsql IMMUTABLE SECURITY DEFINER
COST 1;
ALTER FUNCTION public.clients_check_17_00()OWNER TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_check_17_00() TO postgres;
GRANT EXECUTE ON FUNCTION public.clients_check_17_00() TO "FullAccess";
REVOKE ALL ON FUNCTION public.clients_check_17_00() FROM public;
Some users should have to update comments only before 17:00. So they have permissions on
public.clients_test
and have no permissions on
public.clients_check_17_00
I'd like to have the error 'You do not have permissions to execute public.clients_check_17_00', but this did not work.
This is an interesting problem, but your approach is all wrong.
Rather than checking for the time inside the function, you should manipulate permissions outside of the function. In your approach, the time is checked on every call, also at 08:02, 14:23, 16:34, etc, which is very inefficient for the obvious reasons. Instead, make two simple SQL script files, one to disable execute permissions on the function and another to enable those permissions again and have a scheduled job run those scripts at 17:00 and then presumably at 08:00 or so to re-enable execute permission. It could be as simple as:
psql -h localhost -d your_db -U postgres \
-c 'REVOKE EXECUTE ON FUNCTION clients_test(int, varchar) FROM "RestrictedAccess"'
And vice-versa for enabling the permission again. But see the documentation for parameters specific to accessing your server.
How exactly that works depends on your OS; in Linux you would use a cron job, on Windows you use Task Scheduler.
Incidentally, returning a message from a function to the session is done with RAISE NOTICE.
Another important point from your code: NEVER USE THE postgres ROLE AS OWNER OF OBJECTS OR TO RUN CODE. Apologies for EMPHASIS but this point can not be stressed enough. You should instead make a non-privileged role, preferably without login privilege, as owner of all objects (tables and functions, etc) and then explicitly set privileges for specific user roles.
I have two similar schemas in a single database with the same function names.
Each schema is owned by a role that matches the schema name.
I have issues about function name resolution with nested functions.
I was expecting that the outer function would call inner functions within the same schema, but it does not!
The name is resolved dynamically based on the search_path at run time which make some sens, but not as I would.
Here is a test case. Let say for example that the schemas and roles are named test and prod as follow.
Test schema:
CREATE ROLE test NOLOGIN;
CREATE SCHEMA test AUTHORIZATION test;
CREATE OR REPLACE FUNCTION test.inner_func() RETURNS TEXT
AS $BODY$
BEGIN
RETURN 'test function';
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION test.inner_func() OWNER TO test;
CREATE OR REPLACE FUNCTION test.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
RETURN QUERY SELECT inner_func();
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION test.outer_func() OWNER TO test;
Prod schema:
CREATE ROLE prod NOLOGIN;
CREATE SCHEMA prod AUTHORIZATION prod;
CREATE OR REPLACE FUNCTION prod.inner_func() RETURNS TEXT
AS $BODY$
BEGIN
RETURN 'prod function';
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION prod.inner_func() OWNER TO prod;
CREATE OR REPLACE FUNCTION prod.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
RETURN QUERY SELECT inner_func();
END
$BODY$ LANGUAGE 'plpgsql';
ALTER FUNCTION prod.outer_func() OWNER TO prod;
Test cases:
SET search_path=test,public;
SELECT outer_func();
> test function
SELECT prod.outer_func();
> test function <<<---- was expecting prod function
SET search_path=prod,public;
SELECT prod.outer_func();
> prod function
The test shows that function names are resolved dynamically based on the search_path at run time. Is there a way to bind inner function within the scope of a schema?
I can get such a behavior by using SECURITY DEFINER functions with dynamic SQL and CURRENT_USER, but I am looking for something more straightforward.
The clean solution is to either schema-qualify the function:
CREATE OR REPLACE FUNCTION test.outer_func()
RETURNS SETOF text AS
$func$
BEGIN
RETURN QUERY SELECT test.inner_func();
END
$func$ LANGUAGE plpgsql; -- no quotes!
Or you explicitly set the search_path per function. You can set configuration parameters this way:
CREATE OR REPLACE FUNCTION test.outer_func()
RETURNS SETOF text AS
$func$
BEGIN
RETURN QUERY SELECT inner_func();
END
$func$ LANGUAGE plpgsql SET search_path = test, pg_temp;
Customize the search_path to your needs, possibly add public to the list. I put pg_temp at the end, so objects in the temporary schema cannot hide persisted objects. (But that's not applicable for functions.) Similar to what's explained in the manual for SECURITY DEFINER functions.
I would not advise to rely on the user setting the proper search_path. That would only make sense for "public" functions, it wouldn't be consistent with your design. Why create separate functions and then still have to rely on user settings after all? You could have a single function in the public schema to begin with, but I would not got that route in any case. Very confusing and error prone.
Also, PL/pgSQL executes statements like prepared statements internally. every time you change the search_path, all "prepared" statements from plpgsql functions have to be de-allocated, which is not helping to optimize performance.
Actually, your test case in the question only works if you set the search_path first:
SET search_path=test,public;
Else you get an error when trying to create
CREATE OR REPLACE FUNCTION test.outer_func() RETURNS SETOF TEXT
AS $BODY$
BEGIN
RETURN QUERY SELECT inner_func();
...
ERROR: function inner_func() does not exist
Syntax checks are run against the current search_path at creation time - unless you provide the search_path as suggested. That was fixed 2010 after I reported a bug.
Details for search_path:
How does the search_path influence identifier resolution and the "current schema"
How can I fake inet_client_addr() for unit tests in PostgreSQL?
And don't quote the language name. It's an identifier.
I've a problem with postgresql function that is executed from multiple sessions. The function foo is created in multiple sessions at the exact same time. That causes the login function to be executed at the same time.
Inside the login function (see below) there is a if-statement IF token does not exists THEN that also is executed at the exact same time. Of course, it will be false for all the function since the token never has been created yet.
This is not the behaviour I want. Is it some how possible to make the login function being "thread-safe". Like synchronized in Java?
Pseudo function:
CREATE OR REPLACE FUNCTION foo() RETURNS VARCHAR AS
$body$
BEGIN
token = login();
result = doStuffWithTheLogin(token);
IF result = LoginFailed THEN
token = login(true);
RETURN 'RETRY';
END IF;
END;
$body$
LANGUAGE 'plpgsql'
SECURITY DEFINER;
The login function:
CREATE OR REPLACE FUNCTION login(forceReload BOOLEAN) RETURNS VARCHAR AS
$body$
BEGIN
IF NOT forceReload THEN
token = getTokenFromStorage();
END IF;
IF token does not exists THEN
token = createNewToken();
saveTokenToStorage(token);
END IF;
RETURN token;
END;
$body$
LANGUAGE 'plpgsql'
SECURITY DEFINER;
I've tried with table locks. But that didn't really do the job. The login function takes about 2-3 seconds to finish, if there is 100 foo tasks, the table will be locked for a long time.
It's not very clear what issue you're having or trying to solve, but it sounds like you're looking for an advisory lock:
http://www.postgresql.org/docs/current/static/explicit-locking.html
And perhaps exception handling:
http://www.postgresql.org/docs/current/static/plpgsql-errors-and-messages.html
If I set up an AFTER trigger in PostgreSQL to fire after an insert/update, will the calling software have to wait for the trigger to finish before returning control to the calling software? Or will the trigger run on its own behind the scenes?
Yes, because it's executed within the same transaction. If the trigger fails, the insert/update will also fail. Just do a test executing a query that will fail (SELECT a table that does not exist) and you can see how things work and how your application will behave.
CREATE OR REPLACE FUNCTION foo() RETURNS TRIGGER
AS
$$
BEGIN
EXECUTE 'SELECT fail';
END;
$$
LANGUAGE plpgsql;