I'm trying to translate an SQL Server transaction from a textbook into PostgreSQL. The original transaction is
BEGIN TRANSACTION
INSERT INTO Customers(cust_id,cust_name) VALUES('1000000010','Toys Emporium');
SAVE TRANSACTION StartOrder;
INSERT INTO Orders(order_num,order_date,cust_id) VALUES(20100,'1999/12/1','1000000010');
IF ##ERROR <> 0 ROLLBACK TRANSACTION StartOrder;
[additional inserts with same rollback omitted]
COMMIT TRANSACTION
But I keep getting response
ERROR: syntax error at or near "IF"
LINE 1: IF ##ERROR <> 0 THEN
^
PostgreSQL has the best architecture for developing SQL codes. So, inside the function, you can not use transaction commit or start. Transactions work only inside the procedures. But, you can create your best ACID structure only by using functions. Because the function begin keyword is equivalent to start transaction command and the end keyword of the function is equivalent to commit transaction command. One function = One transaction. But if you need to use one transaction for inserting data into two tables, you can use your insert functions for the same tables inside the one function. In this variant, your main function will be your main transaction. Inside the main function, if one of the internal functions will have an exception so, other internal functions will be rollbacked. For example, we use sub-functions and sub-sub functions inside the main function. If one of the sub-sub functions will have exceptions then your main function will be rollbacked. Using this mechanism you will control your transactions fully, without any problems.
I wrote your query in PostgreSQL, Example:
begin transaction;
INSERT INTO Customers(cust_id,cust_name) VALUES('1000000010','Toys Emporium');
savepoint StartOrder;
commit;
INSERT INTO Orders(order_num,order_date,cust_id) VALUES(20100,'1999/12/1','1000000010');
commit;
exception when others then
rollback to StartOrder;
commit;
Related
With regard to my previous post , I am trying to avoid schema prefixes by setting search_paths, however, I want to limit the scope as much as possible so I'm doing this:
BEGIN TRANSACTION;
SET LOCAL search_path to mySchema;
CALL STORED_PROCEDURE();
UPDATE TABLE MYTABLE1(//SOME CODE);
COMMIT;
CALL STORED_PROCEDURE();
Now, the proc has it's own begin, commit statements, so I'm wondering if the procedure will respect it's outer transaction, i.e.
Will the stored_procedure respect the search_path set by it's parent transaction block?
if updating the table fails, will operations done inside the procedure also rollback?
The BEGIN and END in a PL/pgSQL procedure has no connection with the transaction commands BEGIN and COMMIT/END. They just mark a block.
The procedure will use your search_path, unless you override that inside your procedure. A COMMIT inside the procedure could require special consideration, but that is not a problem here, since you are not allowed to COMMIT inside a procedure that is called from a multi-statement transaction anyway.
I have some procedure which when i execute in dbeaver works fine without issue, however when i call it from outside program i am getting error below. I do not want to copy/paste full procedure here because it's pretty big and it works in db tool. I just copy/paste top and bottom. What could be the cause of it?
Procedure:
CREATE OR REPLACE PROCEDURE MyProcedure(lot of args..)
LANGUAGE plpgsql
AS $procedure$
DECLARE
.....
.....
COMMIT;
END;
$procedure$
;
Error:
ERROR: invalid transaction termination
Where: PL/pgSQL function MyFunction line 185 at COMMIT Call getNextException to see other errors in the batch. Line 185 at COMMIT Call getNextException to see other errors in the batch.
The documentation says:
Transaction control is only possible in CALL or DO invocations from the top level or nested CALL or DO invocations without any other intervening command. For example, if the call stack is CALL proc1() → CALL proc2() → CALL proc3(), then the second and third procedures can perform transaction control actions. But if the call stack is CALL proc1() → SELECT func2() → CALL proc3(), then the last procedure cannot do transaction control, because of the SELECT in between.
There are some other, undocumented, restrictions:
You cannot start a transaction explicitly with BEGIN and commit it inside a transaction. So the following will fail:
START TRANSACTION;
CALL procedure_with_commit();
This may be improved in future releases.
All procedures in the call stack must be written in PL/pgSQL:
CREATE PROCEDURE b() LANGUAGE plpgsql
AS 'BEGIN PERFORM 42; COMMIT; END;';
CREATE PROCEDURE a() LANGUAGE sql
AS 'CALL b()';
CALL a();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function b() line 1 at COMMIT
SQL function "a" statement 1
As it is, transaction control inside PostgreSQL procedures is somewhat limited.
If you violate any of these rules, you will get the error message you describe in your question. You will probably have to handle transactions in the application rather than in the procedure — perhaps splitting the procedure into smaller parts makes this possible.
I have some procedure which when i execute in dbeaver works fine without issue, however when i call it from outside program i am getting error below. I do not want to copy/paste full procedure here because it's pretty big and it works in db tool. I just copy/paste top and bottom. What could be the cause of it?
Procedure:
CREATE OR REPLACE PROCEDURE MyProcedure(lot of args..)
LANGUAGE plpgsql
AS $procedure$
DECLARE
.....
.....
COMMIT;
END;
$procedure$
;
Error:
ERROR: invalid transaction termination
Where: PL/pgSQL function MyFunction line 185 at COMMIT Call getNextException to see other errors in the batch. Line 185 at COMMIT Call getNextException to see other errors in the batch.
The documentation says:
Transaction control is only possible in CALL or DO invocations from the top level or nested CALL or DO invocations without any other intervening command. For example, if the call stack is CALL proc1() → CALL proc2() → CALL proc3(), then the second and third procedures can perform transaction control actions. But if the call stack is CALL proc1() → SELECT func2() → CALL proc3(), then the last procedure cannot do transaction control, because of the SELECT in between.
There are some other, undocumented, restrictions:
You cannot start a transaction explicitly with BEGIN and commit it inside a transaction. So the following will fail:
START TRANSACTION;
CALL procedure_with_commit();
This may be improved in future releases.
All procedures in the call stack must be written in PL/pgSQL:
CREATE PROCEDURE b() LANGUAGE plpgsql
AS 'BEGIN PERFORM 42; COMMIT; END;';
CREATE PROCEDURE a() LANGUAGE sql
AS 'CALL b()';
CALL a();
ERROR: invalid transaction termination
CONTEXT: PL/pgSQL function b() line 1 at COMMIT
SQL function "a" statement 1
As it is, transaction control inside PostgreSQL procedures is somewhat limited.
If you violate any of these rules, you will get the error message you describe in your question. You will probably have to handle transactions in the application rather than in the procedure — perhaps splitting the procedure into smaller parts makes this possible.
I want to use dblink in PL/pgSQL stored procedure in such way:
PERFORM dblink_exec('myconn', 'BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE');
PERFORM dblink_exec('myconn', 'SELECT another_stored_procedure()');
PERFORM dblink_exec('myconn', 'COMMIT');
but I got an error in runtime:
ERROR: statement returning results not allowed
CONTEXT: SQL statement "SELECT dblink_exec('myconn', 'select another_stored_procedure()')"
so execution fails, although I tried to get the desired result in different ways.
UPDATE 1:
I know that stored procedures in postgresql are transactional.
I'm using dblink for the autonomous transactions functionality to use it on the same server.
The matter is that the default level of transactions on my server
is "read commited" but sometimes I need to start transactions with another level, e.g. "serializable".
So I need to execute stored procedure in autonomous transaction with explicit transaction level specifying.
And as far as I know dblink allows that, but I failed to find any useful info about dblink or dblink_exec functions which are suitable for my situation.
I assume, you have connected with another PostgreSQL server at the other end.
You need to call the dblink() function to execute statements, which has result(s), and not dblink_exec(). (Even if your function on the other end has returns void -- in that case, you could get a single NULL from calling that function in a SELECT.)
Also, you might not need transaction management:
Are PostgreSQL functions transactional?
In short, you need to execute:
-- PERFORM dblink_exec('myconn', 'BEGIN ...');
-- if you need explicit transaction management
PERFORM * FROM dblink('myconn', 'SELECT another_stored_procedure()') alias(col text);
-- PERFORM dblink_exec('myconn', 'COMMIT');
Is a PostgreSQL function such as the following automatically transactional?
CREATE OR REPLACE FUNCTION refresh_materialized_view(name)
RETURNS integer AS
$BODY$
DECLARE
_table_name ALIAS FOR $1;
_entry materialized_views%ROWTYPE;
_result INT;
BEGIN
EXECUTE 'TRUNCATE TABLE ' || _table_name;
UPDATE materialized_views
SET last_refresh = CURRENT_TIMESTAMP
WHERE table_name = _table_name;
RETURN 1;
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
In other words, if an error occurs during the execution of the function, will any changes be rolled back? If this isn't the default behavior, how can I make the function transactional?
PostgreSQL 12 update: there is limited support for top-level PROCEDUREs that can do transaction control. You still cannot manage transactions in regular SQL-callable functions, so the below remains true except when using the new top-level procedures.
Functions are part of the transaction they're called from. Their effects are rolled back if the transaction rolls back. Their work commits if the transaction commits. Any BEGIN ... EXCEPT blocks within the function operate like (and under the hood use) savepoints like the SAVEPOINT and ROLLBACK TO SAVEPOINT SQL statements.
The function either succeeds in its entirety or fails in its entirety, barring BEGIN ... EXCEPT error handling. If an error is raised within the function and not handled, the transaction calling the function is aborted. Aborted transactions cannot commit, and if they try to commit the COMMIT is treated as ROLLBACK, same as for any other transaction in error. Observe:
regress=# BEGIN;
BEGIN
regress=# SELECT 1/0;
ERROR: division by zero
regress=# COMMIT;
ROLLBACK
See how the transaction, which is in the error state due to the zero division, rolls back on COMMIT?
If you call a function without an explicit surounding transaction the rules are exactly the same as for any other Pg statement:
BEGIN;
SELECT refresh_materialized_view(name);
COMMIT;
(where COMMIT will fail if the SELECT raised an error).
PostgreSQL does not (yet) support autonomous transactions in functions, where the procedure/function could commit/rollback independently of the calling transaction. This can be simulated using a new session via dblink.
BUT, things that aren't transactional or are imperfectly transactional exist in PostgreSQL. If it has non-transactional behaviour in a normal BEGIN; do stuff; COMMIT; block, it has non-transactional behaviour in a function too. For example, nextval and setval, TRUNCATE, etc.
As my knowledge of PostgreSQL is less deeper than Craig Ringer´s I will try to give a shorter answer: Yes.
If you execute a function that has an error in it, none of the steps will impact in the database.
Also, if you execute a query in PgAdmin the same happen.
For example, if you execute in a query:
update your_table yt set column1 = 10 where yt.id=20;
select anything_that_do_not_exists;
The update in the row, id = 20 of your_table will not be saved in the database.
UPDATE Sep - 2018
To clarify the concept I have made a little example with non-transactional function nextval.
First, let´s create a sequence:
create sequence test_sequence start 100;
Then, let´s execute:
update your_table yt set column1 = 10 where yt.id=20;
select nextval('test_sequence');
select anything_that_do_not_exists;
Now, if we open another query and execute
select nextval('test_sequence');
We will get 101 because the first value (100) was used in the latter query (that is because the sequences are not transactional) although the update was not committed.
https://www.postgresql.org/docs/current/static/plpgsql-structure.html
It is important not to confuse the use of BEGIN/END for grouping statements in PL/pgSQL with the similarly-named SQL commands for transaction control. PL/pgSQL's BEGIN/END are only for grouping; they do not start or end a transaction. Functions and trigger procedures are always executed within a transaction established by an outer query — they cannot start or commit that transaction, since there would be no context for them to execute in. However, a block containing an EXCEPTION clause effectively forms a subtransaction that can be rolled back without affecting the outer transaction. For more about that see Section 39.6.6.
In the function level, it is not transnational. In other words, each statement in the function belongs to a single transaction, which is the default db auto commit value. Auto commit is true by default. But anyway, you have to call the function using
select schemaName.functionName()
The above statement 'select schemaName.functionName()' is a single transaction, let's name the transaction T1, and so the all the statements in the function belong to the transaction T1. In this way, the function is in a single transaction.
Postgres 14 update: All statements written in between the BEGIN and END block of a Procedure/Function is executed in a single transaction. Thus, any errors arising while execution of this block will cause automatic roll back of the transaction.
Additionally, the ATOMIC Transaction including triggers as well.