diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ababd371dfa..65bb0a6a3f3 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -584,7 +584,8 @@ EOF
psql to issue a BEGIN command
before the first such option and a COMMIT command after
the last one, thereby wrapping all the commands into a single
- transaction. If any of the commands fails, a
+ transaction. If any of the commands fails and the variable
+ ON_ERROR_STOP was set, a
ROLLBACK command is sent instead. This ensures that
either all the commands complete successfully, or no changes are
applied.
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 574f49d4c9b..7c2f555f15c 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -426,8 +426,13 @@ main(int argc, char *argv[])
if (options.single_txn)
{
- res = PSQLexec((successResult == EXIT_SUCCESS) ?
- "COMMIT" : "ROLLBACK");
+ /*
+ * Rollback the contents of the single transaction if the caller
+ * has set ON_ERROR_STOP and one of the steps has failed. This
+ * check needs to match the one done a couple of lines above.
+ */
+ res = PSQLexec((successResult != EXIT_SUCCESS && pset.on_error_stop) ?
+ "ROLLBACK" : "COMMIT");
if (res == NULL)
{
if (pset.on_error_stop)
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 44997467bf2..57486ceffdb 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -204,6 +204,8 @@ like(
# query result.
my $tempdir = PostgreSQL::Test::Utils::tempdir;
$node->safe_psql('postgres', "CREATE TABLE tab_psql_single (a int);");
+
+# Tests with ON_ERROR_STOP.
$node->command_ok(
[
'psql', '-X',
@@ -212,11 +214,12 @@ $node->command_ok(
'INSERT INTO tab_psql_single VALUES (1)', '-c',
'INSERT INTO tab_psql_single VALUES (2)'
],
- '--single-transaction and multiple -c switches');
+ 'ON_ERROR_STOP, --single-transaction and multiple -c switches');
my $row_count =
$node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '2',
- '--single-transaction commits transaction, multiple -c switches');
+ '--single-transaction commits transaction, ON_ERROR_STOP and multiple -c switches'
+);
$node->command_fails(
[
@@ -226,11 +229,12 @@ $node->command_fails(
'INSERT INTO tab_psql_single VALUES (3)', '-c',
"\\copy tab_psql_single FROM '$tempdir/nonexistent'"
],
- '--single-transaction and multiple -c switches, error');
+ 'ON_ERROR_STOP, --single-transaction and multiple -c switches, error');
$row_count =
$node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '2',
- 'client-side error rolls back transaction, multiple -c switches');
+ 'client-side error rolls back transaction, ON_ERROR_STOP and multiple -c switches'
+);
# Tests mixing files and commands.
my $copy_sql_file = "$tempdir/tab_copy.sql";
@@ -244,11 +248,12 @@ $node->command_ok(
'ON_ERROR_STOP=1', '-f', $insert_sql_file, '-f',
$insert_sql_file
],
- '--single-transaction and multiple -f switches');
+ 'ON_ERROR_STOP, --single-transaction and multiple -f switches');
$row_count =
$node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '4',
- '--single-transaction commits transaction, multiple -f switches');
+ '--single-transaction commits transaction, ON_ERROR_STOP and multiple -f switches'
+);
$node->command_fails(
[
@@ -256,10 +261,61 @@ $node->command_fails(
'ON_ERROR_STOP=1', '-f', $insert_sql_file, '-f',
$copy_sql_file
],
- '--single-transaction and multiple -f switches, error');
+ 'ON_ERROR_STOP, --single-transaction and multiple -f switches, error');
$row_count =
$node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '4',
- 'client-side error rolls back transaction, multiple -f switches');
+ 'client-side error rolls back transaction, ON_ERROR_STOP and multiple -f switches'
+);
+
+# Tests without ON_ERROR_STOP.
+# The last switch fails on \copy. The command returns a failure and the
+# transaction commits.
+$node->command_fails(
+ [
+ 'psql', '-X',
+ '--single-transaction', '-f',
+ $insert_sql_file, '-f',
+ $insert_sql_file, '-c',
+ "\\copy tab_psql_single FROM '$tempdir/nonexistent'"
+ ],
+ 'no ON_ERROR_STOP, --single-transaction and multiple -f/-c switches');
+$row_count =
+ $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
+is($row_count, '6',
+ 'client-side error commits transaction, no ON_ERROR_STOP and multiple -f/-c switches'
+);
+
+# The last switch fails on \copy coming from an input file. The command
+# returns a success and the transaction commits.
+$node->command_ok(
+ [
+ 'psql', '-X', '--single-transaction', '-f',
+ $insert_sql_file, '-f', $insert_sql_file, '-f',
+ $copy_sql_file
+ ],
+ 'no ON_ERROR_STOP, --single-transaction and multiple -f switches');
+$row_count =
+ $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
+is($row_count, '8',
+ 'client-side error commits transaction, no ON_ERROR_STOP and multiple -f switches'
+);
+
+# The last switch makes the command return a success, and the contents of
+# the transaction commit even if there is a failure in-between.
+$node->command_ok(
+ [
+ 'psql', '-X',
+ '--single-transaction', '-c',
+ 'INSERT INTO tab_psql_single VALUES (5)', '-f',
+ $copy_sql_file, '-c',
+ 'INSERT INTO tab_psql_single VALUES (6)'
+ ],
+ 'no ON_ERROR_STOP, --single-transaction and multiple -c switches');
+$row_count =
+ $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
+is($row_count, '10',
+ 'client-side error commits transaction, no ON_ERROR_STOP and multiple -c switches'
+);
done_testing();