dblink_connect() runs as the database cluster user inside the database, usually “postgres”. You’ll need to create a separate .pgpass file for that user *on your Postgres server*. Some more info here:
https://stackoverflow.com/q/37991825/79202
Sweet! Care to share with us a little howto?
Sure, here is a howto.
If you want to test your DEFERRABLE INITIALLY DEFERRED triggers with the help of dblink, your first need to prepare your test database by executing the following commands as a superuser.
SET client_min_messages TO WARNING;Note that I create a pgtap schema and install the pgTAP extension in this schema to avoid repeating this code in all test files.
The use of the function dblink_connect_u instead dblink_connect allows you to make use of a .pgpass file to store passwords in order to connect. You may already be using another .pgpass file on your client machine but this is different. A new .pgpass file is still needed and will have to be stored on your server, in the home folder of the user running PostgreSQL. So the next step is to create this .pgpass file and place it on your server machine, in my case, my server runs on xubuntu and the user running PostgreSQL is postgres whose home folder is: /var/lib/postgresql. So I created /var/lib/postgresql/.pgpass as:
localhost:5432:*:ownerrole:98NQqhMnbMbV6DmraiyBuPosjAnryCALg9gFTunUbFUUnUCCt9sQaaJdzxm3ZJCkThe next step is to write a test case with the help of dblink. Here is an example:
SET search_path TO pgtap, public;
BEGIN;
SELECT plan(2);
--------------------------------------------------------------------------------
-- Test 1: Check that the commit throws an error
--------------------------------------------------------------------------------
CREATE FUNCTION pg_temp.commit_throws(IN in_connstr text)
RETURNS void
LANGUAGE plpgsql
VOLATILE
RETURNS NULL ON NULL INPUT
AS $body$
BEGIN
PERFORM dblink_connect_u('unittest', in_connstr);
PERFORM dblink_exec('unittest', 'BEGIN;');
-- Commands that should lead to an inconsistent state
PERFORM dblink_exec('unittest', 'INSERT INTO …;');
-- Attempt a commit
BEGIN
PERFORM dblink_exec('unittest', 'COMMIT;');
EXCEPTION
WHEN SQLSTATE 'U1001' THEN
RAISE LOG 'The commit failed!';
PERFORM dblink_disconnect('unittest');
RAISE;
END;
PERFORM dblink_disconnect('unittest');
END;$body$;
PREPARE plan_1_commit_throws AS
SELECT
pg_temp.commit_throws(:'ff_connstr_ownerrole');
SELECT throws_ok(
$$
EXECUTE plan_1_commit_throws
$$,
'U1001',
'here the error message supposed to be raised.'
);
--------------------------------------------------------------------------------
-- Test 2: Check that the commit does not throw an error
--------------------------------------------------------------------------------
CREATE FUNCTION pg_temp.commit_lives(IN in_connstr text)
RETURNS void
LANGUAGE plpgsql
VOLATILE
RETURNS NULL ON NULL INPUT
AS $body$
BEGIN
PERFORM dblink_connect_u('unittest', in_connstr);
PERFORM dblink_exec('unittest', 'BEGIN;');
-- Commands that should not lead to an inconsistent
state
PERFORM dblink_exec('unittest', 'INSERT INTO …;');
-- Attempt a commit
BEGIN
PERFORM dblink_exec('unittest', 'COMMIT;');
EXCEPTION
WHEN SQLSTATE 'U1001' THEN
RAISE LOG 'The commit failed!';
PERFORM dblink_disconnect('unittest');
RAISE;
END;
PERFORM dblink_disconnect('unittest');
END;$body$;
PREPARE plan_2_commit_lives AS
SELECT pg_temp.commit_lives(:'ff_connstr_ownerrole');
SELECT lives_ok(
$$
EXECUTE plan_2_commit_lives
$$
);
--------------------------------------------------------------------------------
-- Cleanup
--------------------------------------------------------------------------------
-- Drop the test functions
DROP FUNCTION pg_temp.commit_throws(text);
DROP FUNCTION pg_temp.commit_lives(text);
-- Cleanup whatever has been committed
DELETE FROM …;
SELECT finish();
-- Here it is important to use COMMIT and not ROLLBACK if
you want your cleanup to be applied
COMMIT;
To test a DEFERRABLE INITIALLY DEFERRED triggers, you will create a PL/pgSQL function to create a connection, start a transaction, run SQL commands that will leads or not to an error and then attempt to commit the transaction. In case the commit attempt fails, you can catch the exception to cleanly disconnect before re-raising the exception.
Doing that allows you to use the PL/pgSQL function very easily with pgTAP throws_ok and lives_ok functions.
Note that I build the connection string used by dblink_connect_u before running the test file. I then pass it via psql variables :'ff_connstr_ownerrole'.
Hope this helps!
Daniel
I put boilerplate like this at the top of all my test scripts. SET client_min_messages TO warning; CREATE EXTENSION IF NOT EXISTS pgtap; RESET client_min_messages; This way it doesn’t matter whether I ever remember to set things up in advance; if I run tests, all the stuff they need is there.
The next step is to write a test case with the help of dblink. Here is an example: SET search_path TO pgtap, public; BEGIN; SELECT plan(2); -------------------------------------------------------------------------------- -- Test 1: Check that the commit throws an error -------------------------------------------------------------------------------- CREATE FUNCTION pg_temp.commit_throws(IN in_connstr text)Huh. Why in pg_temp?
PREPARE plan_1_commit_throws AS SELECT pg_temp.commit_throws(:'ff_connstr_ownerrole'); SELECT throws_ok( $$ EXECUTE plan_1_commit_throws $$, 'U1001', 'here the error message supposed to be raised.' );I might have made commit_throws return a boolean. Then it’s just SELECT ok( pg_temp.commit_throws(:'ff_connstr_ownerrole’), 'here the error message supposed to be raised.’ );
To test a DEFERRABLE INITIALLY DEFERRED triggers, you will create a PL/pgSQL function to create a connection, start a transaction, run SQL commands that will leads or not to an error and then attempt to commit the transaction. In case the commit attempt fails, you can catch the exception to cleanly disconnect before re-raising the exception. Doing that allows you to use the PL/pgSQL function very easily with pgTAP throws_ok and lives_ok functions.You can also use DO and skip creating functions. https://www.postgresql.org/docs/current/static/sql-do.html
Note that I build the connection string used by dblink_connect_u before running the test file. I then pass it via psql variables :'ff_connstr_ownerrole’.If it’s the same database, user, etc., you can take advantage of the system info functions, such ass current_database(), current_role(), etc. https://www.postgresql.org/docs/10/static/functions-info.html
Thanks!
Thank you David for your lovely tool which I enjoy using in combination with another of your lovely tool: Sqitch.
Daniel
you can skip dropping the functions if you want (or expect them to be dropped automatically if your test fails for some other reason). Though they should be dropped by the ROLLBACK, anyway.
At this point, assuming the test passes, your database is left with data you inserted into the table. If you want to clean that out, to leave the database in the same state in which you found it, you’ll need to delete the relevant record(s) or truncate the relevant tables. You’d need to do that cleanup over dblink, too.That is right, this is what I do in the "Cleanup" section that you did not quote here.You mean this?-------------------------------------------------------------------------------- -- Cleanup -------------------------------------------------------------------------------- -- Drop the test functions DROP FUNCTION pg_temp.commit_throws(text); DROP FUNCTION pg_temp.commit_lives(text); -- Cleanup whatever has been committed DELETE FROM …; SELECT finish(); -- Here it is important to use COMMIT and not ROLLBACK if you want your cleanup to be applied COMMIT;
That will not clean out data committed in the dblink connection.
I just double-checked and it does clean up my data. In my actual
test file, I first verify that the database is in order to avoid
false positives. I use pgTAP for that:
If I comment out the cleanup part (what is just under the "-- Cleanup whatever has been committed"), then I can run the test file successfully the first time but the second time, it fails thanks to the above check.
Daniel
I just double-checked and it does clean up my data. In my actual test file, I first verify that the database is in order to avoid false positives. I use pgTAP for that: SELECT is( EXISTS (SELECT 1 FROM mytable), FALSE ); If I comment out the cleanup part (what is just under the "-- Cleanup whatever has been committed"), then I can run the test file successfully the first time but the second time, it fails thanks to the above check.I bet it wouldn’t if you used SERIALIZABLE isolation, not without first committing, then deleting, then committing again. I forget that READ COMMITTED is the default. https://www.postgresql.org/docs/10/static/transaction-iso.html
I am using READ COMMITTED isolation level. I tested with SERIALIZABLE (for all transactions) and it did not clean up, indeed.
Daniel