Tweak behavior of psql --single-transaction depending on ON_ERROR_STOP
authorMichael Paquier <[email protected]>
Wed, 15 Jun 2022 02:24:52 +0000 (11:24 +0900)
committerMichael Paquier <[email protected]>
Wed, 15 Jun 2022 02:24:52 +0000 (11:24 +0900)
This commit, in completion of 157f873, forces a ROLLBACK for
--single-transaction only when ON_ERROR_STOP is used when one of the
steps defined by -f/-c fails.  Hence, COMMIT is always used when
ON_ERROR_STOP is not set, ignoring the status code of the last action
taken in the set of switches specified by -c/-f (previously ROLLBACK
would have been issued even without ON_ERROR_STOP if the last step
failed, while COMMIT was issued if a step in-between failed as long as
the last step succeeded, leading to more inconsistency).

While on it, this adds much more test coverage in this area when not
using ON_ERROR_STOP with multiple switch patterns involving -c and -f
for query files, single queries and slash commands.

The behavior of ON_ERROR_STOP is arguably a bug, but there was no much
support for a backpatch to force a ROLLBACK on a step failure, so this
change is done only on HEAD for now.

Per discussion with Tom Lane and Kyotaro Horiguchi.

Discussion: https://postgr.es/m/[email protected]

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/startup.c
src/bin/psql/t/001_basic.pl

index ababd371dfac93156525b1a5e253b98535471e45..65bb0a6a3f3328b8140bd3f0aa886ae4b650973b 100644 (file)
@@ -584,7 +584,8 @@ EOF
         <application>psql</application> to issue a <command>BEGIN</command> command
         before the first such option and a <command>COMMIT</command> 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
+        <varname>ON_ERROR_STOP</varname> was set, a
         <command>ROLLBACK</command> command is sent instead. This ensures that
         either all the commands complete successfully, or no changes are
         applied.
index 574f49d4c9bc3f1c5486917a8614e1f21b381f59..7c2f555f15c105ad7ae2360786a4308b4d7f5156 100644 (file)
@@ -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)
index 44997467bf2b0ef9baddb86007e138a5145aaa5d..57486ceffdb70b729470df490c02fa8a36984b2b 100644 (file)
@@ -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();