16.3. Examples#
16.3. Examples #
This chapter provides examples of using autonomous transactions
in PL/pgSQL
functions. Autonomous transactions
allow executing SQL statements in a separate transaction within a PL/pgSQL
function for implementing complex logic and error handling.
To use autonomous transactions
, the PRAGMA AUTONOMOUS_TRANSACTION
needs to be declared in the function. This will start an autonomous session that runs SQL statements in an isolated transaction.
The examples demonstrate basic usage patterns like bulk inserts, passing parameters from the main session, and implementing robust transaction control logic.
Autonomous transactions
provide flexibility in designing PL/pgSQL
functions that is not possible with regular exception handling and transactions. However, they should be used carefully to avoid concurrency issues and overhead from launching sessions.
16.3.1. Using Autonomous Transactions in PL/pgSQL
#
Creating a Table for Tracking Logs:
-- Creating a table for log storage DROP TABLE IF EXISTS test1; CREATE TABLE table_tracking( log_id serial, username text, event_date timestamp, msg text );
Creating log_action_atx Function with Autonomous Transaction:
-- Defining a function with autonomous transaction for logging CREATE OR REPLACE FUNCTION log_action_atx ( username text, event_date timestamp, msg text ) RETURNS VOID AS $body$ DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN START TRANSACTION; INSERT INTO table_tracking VALUES ( nextval('table_tracking_log_id_seq'::regclass), username, event_date, msg ); COMMIT; END; $body$ LANGUAGE plpgsql;
Creating the Main Function log_action that Calls log_action_atx:
-- Main function for performing actions and calling the logging function CREATE OR REPLACE FUNCTION log_action ( username text, event_date timestamp, msg text ) RETURNS VOID AS $body$ DECLARE v_query text; BEGIN -- Call the autonomous transaction for logging v_query := 'SELECT log_action_atx ( ' || quote_nullable(username) || ',' || quote_nullable(event_date) || ',' || quote_nullable(msg) || ' )'; EXECUTE v_query; -- Executing additional actions with an error RAISE NOTICE 'Division by zero'; PERFORM 5/0; END; $body$ LANGUAGE PLPGSQL;
Executing the Main Function to Demonstrate its Functionality:
-- Calling the log_action function to demonstrate logging operation SELECT * FROM log_action( current_user::text, now()::timestamp, 'Test'::text );
This code demonstrates using an autonomous transaction
within a PL/pgSQL
function to log information separately from the main transaction. Here is what it is doing:
Creates a table to store the tracking logs.
Creates a
PL/pgSQL
function log_action_atx() declared asAUTONOMOUS TRANSACTION
.This function inserts a log row and commits it immediately.
The main function log_action() calls log_action_atx() to generate the log.
It then does some other actions which raise an error.
When called, the log insert happens in a separate transaction and commits.
The main transaction is then rolled back due to the error.
But the log row persists since it was already committed. It wasn't committed if it wasn't called as
autonomous transactions
.
So in summary, the autonomous transaction
allows logging to occur separately from the main transaction isolation. This ensures logs are not lost on transaction rollback.
The trick is the AUTONOMOUS_TRANSACTION
pragma declaration in log_action_atx().
NOTICE:
Division by zeroERROR:
division by zeroCONTEXT:
SQL statement "SELECT 5/0"PL/pgSQL function log_action(text,timestamp without time zone,text) line 11 at PERFORM
-- Query to check stored logs
SELECT msg FROM table_tracking;
msg
16.3.2. Autonomous transaction within an anonymous block with COMMIT #
Creating a Procedure with an Autonomous Transaction
:
-- Creating a procedure with an autonomous transaction for independent operations CREATE OR REPLACE PROCEDURE insert_two_rows() LANGUAGE plpgsql AS $$ DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO dept VALUES (60, 'FINANCE', 'CHICAGO'); CALL insert_dept_70(); COMMIT; END; $$;
Main Transaction Executing with a Rollback:
-- Executing main transaction with operations and a rollback BEGIN; INSERT INTO dept VALUES (50, 'HR', 'DENVER'); CALL insert_two_rows(); ROLLBACK;
Query to Display the Final State of the Table:
-- Display the final state of the table after transactions test=# SELECT * FROM dept; deptno | dname | loc --------+------------+------------- 10 | ACCOUNTING | NEW YORK 20 | RESEARCH | DALLAS 30 | SALES | CHICAGO 40 | OPERATIONS | BOSTON 60 | FINANCE | CHICAGO 70 | MARKETING | LOS ANGELES (6 rows)
Key Points:
The insert_two_rows procedure uses the directive
PRAGMA AUTONOMOUS_TRANSACTION
, allowing it to execute transactions independently of the main transaction.Within insert_two_rows, there's an insertion of record 60 and a call to insert_dept_70.
The insert_two_rows procedure ends with a COMMIT, persisting the changes made within its
autonomous transaction
.The main transaction executes a ROLLBACK, undoing the insertion of record 50 but not affecting the
autonomous transaction
.
Outcome: Records 60 and 70 are persisted in the table, but not 50.
16.3.3. Autonomous transaction within an anonymous block with COMMIT, including a procedure with ROLLBACK #
Creating a Procedure for Inserting Department 70:
-- Creating a procedure to insert department 70 into the dept table CREATE OR REPLACE PROCEDURE insert_dept_70() LANGUAGE plpgsql AS $$ BEGIN INSERT INTO dept VALUES (70,'MARKETING','LOS ANGELES'); END; $$;
Creating a Procedure with Autonomous Transaction
and Rollback:
-- Creating a procedure with an autonomous transaction that inserts and then rolls back CREATE OR REPLACE PROCEDURE insert_to_values() LANGUAGE plpgsql AS $$ DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN START TRANSACTION; INSERT INTO dept VALUES (60,'FINANCE','CHICAGO'); CALL insert_dept_70(); ROLLBACK; END; $$;
Executing Main Transaction and Committing:
-- Executing main transaction that inserts a record and calls the insert_to_values procedure START TRANSACTION; INSERT INTO dept VALUES (50,'HR','DENVER'); CALL insert_to_values(); COMMIT;
Display the Final State of the Table:
-- Display the final state of the dept table after transactions test=# SELECT * FROM dept; deptno | dname | loc --------+------------+------------- 10 | ACCOUNTING | NEW YORK 20 | RESEARCH | DALLAS 30 | SALES | CHICAGO 40 | OPERATIONS | BOSTON 50 | HR | DENVER 70 | MARKETING | LOS ANGELES (6 rows)
Key Points:
The insert_to_values procedure also utilizes
PRAGMA AUTONOMOUS_TRANSACTION
, ensuring transactional autonomy.Inside insert_to_values, there's an insertion of record 60, a call to insert_dept_70, followed by a ROLLBACK, undoing all changes within this procedure.
The main transaction concludes with a COMMIT, persisting the insertion of record 50.
Outcome: Records 50 and 70 are persisted in the table, but not 60.
The PRAGMA AUTONOMOUS_TRANSACTION
directive allows for autonomous transactions
within procedures, making these transactions independent from the main or outer transactions. Thus, actions like COMMIT or ROLLBACK in an autonomous transaction
don't affect the main transaction and vice versa.
16.3.4. Autonomous Transaction Trigger #
Creating Schema and Tables for User Activity Auditing:
-- Setup for user activity auditing: schema and related tables CREATE SCHEMA audtrig; CREATE TABLE audtrig.user_activity ( id INT, user_name VARCHAR ); CREATE TABLE audtrig.log ( log_time timestamp, user_name varchar, event varchar );
Creating Logging Function with Autonomous Transaction
:
-- Defining a function to autonomously log operations on user_activity CREATE OR REPLACE FUNCTION audtrig.log_func() RETURNS TRIGGER LANGUAGE plpgsql AS $$ DECLARE PRAGMA AUTONOMOUS_TRANSACTION; v_action VARCHAR; BEGIN IF (TG_OP = 'INSERT') THEN v_action := 'Added id=' || NEW.id::text; ELSIF (TG_OP = 'UPDATE') THEN v_action := 'Updated id=' || NEW.id::text; ELSIF (TG_OP = 'DELETE') THEN v_action := 'Deleted id=' || OLD.id::text; END IF; INSERT INTO audtrig.log VALUES (now(), current_user, v_action); RETURN NEW; END; $$;
Creating Trigger for Logging Function Execution:
-- Trigger to capture and log changes in the user_activity table CREATE OR REPLACE TRIGGER audtrig_trig_func AFTER INSERT OR UPDATE OR DELETE ON audtrig.user_activity FOR EACH ROW EXECUTE PROCEDURE audtrig.log_func(); SELECT * FROM audtrig.log; SELECT * FROM audtrig.user_activity;
Executing Data Operations and Logging Them:
-- Inserting and deleting data in user_activity, and checking the resulting logs INSERT INTO audtrig.user_activity VALUES (9001,'SMITH'); INSERT INTO audtrig.user_activity VALUES (9002,'JONES'); DELETE FROM audtrig.user_activity WHERE id=9001; SELECT * FROM audtrig.log; SELECT * FROM audtrig.user_activity;
Testing Rollback of Transaction and Its Effect on Logs:
-- Demonstrating transaction rollback and its impact on logs TRUNCATE audtrig.log; TRUNCATE audtrig.user_activity; START TRANSACTION; INSERT INTO audtrig.user_activity VALUES (9001,'SMITH'); INSERT INTO audtrig.user_activity VALUES (9002,'JONES'); DELETE FROM audtrig.user_activity WHERE id=9001; ROLLBACK; SELECT * FROM audtrig.log; SELECT * FROM audtrig.user_activity;
Cleanup: Dropping Tables and Schema:
-- Cleaning up: Dropping the log and user_activity tables and the audtrig schema DROP TABLE audtrig.log; DROP TABLE audtrig.user_activity;
Operations and Results:
Two records with IDs 9001 and 9002 are inserted into the user_activity table.
The record with ID 9001 is then deleted.
By querying the log table, we can see a log of these operations.
The log and user_activity tables are then truncated (emptied).
A new transaction begins, where the same two records are inserted into user_activity, and then the record with ID 9001 is deleted. However, this transaction is rolled back, so the changes are not persisted.
By querying the log table, we still see the logs of the operations performed within the rolled-back transaction. This is noteworthy because even though the data changes were undone by the rollback, the logs were not.