execute external program with trigger in postgres 9.4
Disclamer: I work with Andreas Fritsch on the same project.
We have solved this problem in the following way.
There is an "Language"-extension PL/sh Procedural Language Handler for PostgreSQL coded by Peter Eisentraut which does exactly what we need.
You define a shell-script like this:
CREATE or REPLACE FUNCTION test(text) RETURNS text AS '
#!/bin/bash
echo Test: $1 is working
' LANGUAGE plsh;
This is an example of a trigger-function with some usefull environment-variables for triggers:
CREATE or REPLACE FUNCTION TriggerTest() RETURNS trigger AS $$
#!/bin/bash
#mkdir /has/triggertest/$PLSH_TG_NAME
cd /has/triggertest
touch PLSH_TG_NAME-$PLSH_TG_NAME
touch PLSH_TG_WHEN-$PLSH_TG_WHEN
touch PLSH_TG_LEVEL-$PLSH_TG_LEVEL
touch PLSH_TG_OP-$PLSH_TG_OP
touch PLSH_TG_TABLE_NAME-$PLSH_TG_TABLE_NAME
touch PLSH_TG_TABLE_SCHEMA-$PLSH_TG_TABLE_SCHEMA
touch new-$new.x
#touch "arg-0-'$0'"
touch "arg-1-'$1'"
touch "arg-2-'$2'"
touch "arg-3-'$3'"
touch "arg-4-'$4'"
for arg do
touch "Arg is '$arg'"
done
exit 0
$$ LANGUAGE plsh;
You create a before-insert-trigger with the following SQL-Statement
CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest2
FOR EACH ROW EXECUTE PROCEDURE TriggerTest(new);
I hope this helps anybody else who is looking for a similar solution for his problem.
Call command-line function in Perl and get output as String
You can use backticks. Quoting from perldoc perlop
:
*qx/STRING/
*`STRING`
A string which is (possibly) interpolated and then
executed as a system command with /bin/sh or its equivalent. Shell
wildcards, pipes, and redirections will be honored. The collected
standard output of the command is returned; standard error is
unaffected. In scalar context, it comes back as a single (potentially
multi-line) string, or undef if the command failed. In list context,
returns a list of lines (however you've defined lines with $/ or
$INPUT_RECORD_SEPARATOR), or an empty list if the command failed.
You can't use system
for this, since it just returns the return value of the argument (when run as a shell command). This should work:
CREATE OR REPLACE FUNCTION perl_func()
RETURNS character varying AS
$BODY$
my $output=`java -version`;
chomp($output);
return $output;
$BODY$
LANGUAGE plperlu VOLATILE
COST 100;
Note that the output of a backticked command usually includes a trailing newline, so it's often useful to get rid of that via chomp
.
Passing user id to PostgreSQL triggers
Options include:
When you open a connection,
CREATE TEMPORARY TABLE current_app_user(username text); INSERT INTO current_app_user(username) VALUES ('the_user');
. Then in your trigger,SELECT username FROM current_app_user
to get the current username, possibly as a subquery.In
postgresql.conf
create an entry for a custom GUC likemy_app.username = 'unknown';
. Whenever you create a connection runSET my_app.username = 'the_user';
. Then in triggers, use thecurrent_setting('my_app.username')
function to obtain the value. Effectively, you're abusing the GUC machinery to provide session variables. Read the documentation appropriate to your server version, as custom GUCs changed in 9.2.Adjust your application so that it has database roles for every application user.
SET ROLE
to that user before doing work. This not only lets you use the built-incurrent_user
variable-like function toSELECT current_user;
, it also allows you to enforce security in the database. See this question. You could log in directly as the user instead of usingSET ROLE
, but that tends to make connection pooling hard.
In both all three cases you're connection pooling you must be careful to DISCARD ALL;
when you return a connection to the pool. (Though it is not documented as doing so, DISCARD ALL
does a RESET ROLE
).
Common setup for demos:
CREATE TABLE tg_demo(blah text);
INSERT INTO tg_demo(blah) VALUES ('spam'),('eggs');
-- Placeholder; will be replaced by demo functions
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$
SELECT 'unknown';
$$ LANGUAGE sql;
CREATE OR REPLACE FUNCTION tg_demo_trigger() RETURNS trigger AS $$
BEGIN
RAISE NOTICE 'Current user is: %',get_app_user();
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tg_demo_tg
AFTER INSERT OR UPDATE OR DELETE ON tg_demo
FOR EACH ROW EXECUTE PROCEDURE tg_demo_trigger();
Using a GUC:
- In the
CUSTOMIZED OPTIONS
section ofpostgresql.conf
, add a line likemyapp.username = 'unknown_user'
. On PostgreSQL versions older than 9.2 you also have to setcustom_variable_classes = 'myapp'
. - Restart PostgreSQL. You will now be able to
SHOW myapp.username
and get the valueunknown_user
.
Now you can use SET myapp.username = 'the_user';
when you establish a connection, or alternately SET LOCAL myapp.username = 'the_user';
after BEGIN
ning a transaction if you want it to be transaction-local, which is convenient for pooled connections.
The get_app_user
function definition:
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$
SELECT current_setting('myapp.username');
$$ LANGUAGE sql;
Demo using SET LOCAL
for transaction-local current username:
regress=> BEGIN;
BEGIN
regress=> SET LOCAL myapp.username = 'test_user';
SET
regress=> INSERT INTO tg_demo(blah) VALUES ('42');
NOTICE: Current user is: test_user
INSERT 0 1
regress=> COMMIT;
COMMIT
regress=> SHOW myapp.username;
myapp.username
----------------
unknown_user
(1 row)
If you use SET
instead of SET LOCAL
the setting won't get reverted at commit/rollback time, so it's persistent across the session. It is still reset by DISCARD ALL
:
regress=> SET myapp.username = 'test';
SET
regress=> SHOW myapp.username;
myapp.username
----------------
test
(1 row)
regress=> DISCARD ALL;
DISCARD ALL
regress=> SHOW myapp.username;
myapp.username
----------------
unknown_user
(1 row)
Also, note that you can't use SET
or SET LOCAL
with server-side bind parameters. If you want to use bind parameters ("prepared statements"), consider using the function form set_config(...)
. See system adminstration functions
Using a temporary table
This approach requires the use of a trigger (or helper function called by a trigger, preferably) that tries to read a value from a temporary table every session should have. If the temporary table cannot be found, a default value is supplied. This is likely to be somewhat slow. Test carefully.
The get_app_user()
definition:
CREATE OR REPLACE FUNCTION get_app_user() RETURNS text AS $$
DECLARE
cur_user text;
BEGIN
BEGIN
cur_user := (SELECT username FROM current_app_user);
EXCEPTION WHEN undefined_table THEN
cur_user := 'unknown_user';
END;
RETURN cur_user;
END;
$$ LANGUAGE plpgsql VOLATILE;
Demo:
regress=> CREATE TEMPORARY TABLE current_app_user(username text);
CREATE TABLE
regress=> INSERT INTO current_app_user(username) VALUES ('testuser');
INSERT 0 1
regress=> INSERT INTO tg_demo(blah) VALUES ('42');
NOTICE: Current user is: testuser
INSERT 0 1
regress=> DISCARD ALL;
DISCARD ALL
regress=> INSERT INTO tg_demo(blah) VALUES ('42');
NOTICE: Current user is: unknown_user
INSERT 0 1
Secure session variables
There's also a proposal to add "secure session variables" to PostgreSQL. These are a bit like package variables. As of PostgreSQL 12 the feature has not been included, but keep an eye out and speak up on the hackers list if this is something you need.
Advanced: your own extension with shared memory area
For advanced uses you can even have your own C extension register a shared memory area and communicate between backends using C function calls that read/write values in a DSA segment. See the PostgreSQL programming examples for details. You'll need C knowledge, time, and patience.
How to track changes in any function in PostgreSQL
In Postgres 9.5 there is a function pg_event_trigger_ddl_commands()
which can be used in an event trigger to get an oid of inserted/altered object.
Log table:
create table function_log (
datetime timestamp,
schema_name text,
function_name text,
tag text,
function_body text);
Event function and trigger:
create or replace function public.on_function_event()
returns event_trigger
language plpgsql
as $function$
begin
insert into function_log
select now(), nspname, proname, command_tag, prosrc
from pg_event_trigger_ddl_commands() e
join pg_proc p on p.oid = e.objid
join pg_namespace n on n.oid = pronamespace;
end
$function$;
create event trigger on_function_event
on ddl_command_end
when tag in ('CREATE FUNCTION', 'ALTER FUNCTION')
execute procedure on_function_event();
Example:
create or replace function test()
returns int as $$ select 1; $$ language sql;
create or replace function test()
returns int as $$ select 2; $$ language sql;
alter function test() immutable;
select *
from function_log;
datetime | schema_name | function_name | tag | function_body
----------------------------+-------------+---------------+-----------------+---------------
2017-02-26 13:05:15.353879 | public | test | CREATE FUNCTION | select 1;
2017-02-26 13:05:15.353879 | public | test | CREATE FUNCTION | select 2;
2017-02-26 13:05:15.353879 | public | test | ALTER FUNCTION | select 2;
(3 rows)
You can add DROP FUNCTION
command tag to the trigger and then use the function pg_event_trigger_dropped_objects()
in analogous way to pg_event_trigger_ddl_commands()
.
Unfortunately, there is no pg_event_trigger_ddl_commands()
in Postgres 9.4. You could try to get an inserted/altered object using current_query()
or write a trigger function in C
. I think the easier way would be to upgrade Postgres to 9.5+.
WildFly 10 with PostgreSQL 9.4 datasource Error deploying application
I have been working with Wildfly10 with posgresql-9.3, defining datasources directly in standalone-full.xml file have worked for me. So I hope this help you.
I have created directories under modules for the PostgreSQL files. The structure is modules/org/postgres/main
Do this in your standalone-full.xml
which is under standalone>configuration
in that file inside <datasources>
add this
<datasource jndi-name="java:jboss/datasources/PAYROLL_DS" pool-name="PAYROLL_DS" enabled="true" use-java-context="true">
<connection-url>jdbc:postgresql://localhost:5432/payroll_db</connection-url>
<driver>postgresql</driver>
<security>
<user-name>test</user-name>
<password>test@123</password>
</security>
</datasource>
this is my persistence.xml
file
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="PAYROLL_PU" transaction-type="JTA">
<jta-data-source>java:/jboss/datasources/PAYROLL_DS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.case.use_query_cache" value="false"/>
<!-- 2nd level cache -->
</properties>
</persistence-unit>
</persistence>
When you add datasources through standalone-full.xml
you dont need to create module.xml
to present postgres
. You just simply edit standalone-full.xml
inside <drivers>
add this
<driver name="postgresql" module="org.postgresql">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver>
Related Topics
Mpc/Mpd on Linux: How to Play Local Wav File
How to Simulate a Iret on Linux X86_64
Example of Using External Libraries or Packages in Common Lisp
How to Make Libusb Library Visible to Another Program
Xfs - How to Not Modify Mtime When Writing to File
How to Store Result of Diff in Linux
How to Clear Docker Task History
Programmatically Set Custom Folder/Directory Icon in Linux
How to Remove File with Special Characters
How to Rename a Bunch of Files to Eliminate Quote Marks
Why Can One Remove/Rename Open Files in Linux
Installing New Version of Python on Debian Linux Server
How to Get The Output of at Command in Current or Another Terminal Window