Hello Teodor,

Patch seems usefull and commitable except comments in conditional.[ch]. I'd like to top/header comment in each file more detailed and descriptive. As for now it mentions only psql usage without explaining how it is basic or common.

Indeed, it was not updated.

I've fixed the file names and added a simple description at the beginning of the header file, and a one liner in the code file.

Do you think that more is needed?

The patch also needed a rebase after the hash function addition.

--
Fabien.
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index f07ddf1..d52d324 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -900,6 +900,21 @@ pgbench <optional> <replaceable>options</replaceable> 
</optional> <replaceable>d
   </para>
 
   <variablelist>
+   <varlistentry>
+    <term><literal>\if</literal> <replaceable 
class="parameter">expression</replaceable></term>
+    <term><literal>\elif</literal> <replaceable 
class="parameter">expression</replaceable></term>
+    <term><literal>\else</literal></term>
+    <term><literal>\endif</literal></term>
+    <listitem>
+     <para> 
+      This group of commands implements nestable conditional blocks,
+      similarly to <literal>psql</literal>'s <xref 
linkend="psql-metacommand-if"/>.
+      Conditional expressions are identical to those with 
<literal>\set</literal>,
+      with non-zero values interpreted as true.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id='pgbench-metacommand-set'>
     <term>
      <literal>\set <replaceable>varname</replaceable> 
<replaceable>expression</replaceable></literal>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index bfdf859..10b9795 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2169,7 +2169,7 @@ hello 10
       </varlistentry>
 
 
-      <varlistentry>
+      <varlistentry id="psql-metacommand-if">
         <term><literal>\if</literal> <replaceable 
class="parameter">expression</replaceable></term>
         <term><literal>\elif</literal> <replaceable 
class="parameter">expression</replaceable></term>
         <term><literal>\else</literal></term>
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index a15aa06..894571e 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -32,6 +32,7 @@
 #endif                                                 /* ! WIN32 */
 
 #include "postgres_fe.h"
+#include "fe_utils/conditional.h"
 
 #include "getopt_long.h"
 #include "libpq-fe.h"
@@ -282,6 +283,9 @@ typedef enum
         * and we enter the CSTATE_SLEEP state to wait for it to expire. Other
         * meta-commands are executed immediately.
         *
+        * CSTATE_SKIP_COMMAND for conditional branches which are not executed,
+        * quickly skip commands that do not need any evaluation.
+        *
         * CSTATE_WAIT_RESULT waits until we get a result set back from the 
server
         * for the current command.
         *
@@ -291,6 +295,7 @@ typedef enum
         * command counter, and loops back to CSTATE_START_COMMAND state.
         */
        CSTATE_START_COMMAND,
+       CSTATE_SKIP_COMMAND,
        CSTATE_WAIT_RESULT,
        CSTATE_SLEEP,
        CSTATE_END_COMMAND,
@@ -320,6 +325,7 @@ typedef struct
        PGconn     *con;                        /* connection handle to DB */
        int                     id;                             /* client No. */
        ConnectionStateEnum state;      /* state machine's current state. */
+       ConditionalStack cstack;        /* enclosing conditionals state */
 
        int                     use_file;               /* index in sql_script 
for this client */
        int                     command;                /* command number in 
script */
@@ -408,7 +414,11 @@ typedef enum MetaCommand
        META_SET,                                       /* \set */
        META_SETSHELL,                          /* \setshell */
        META_SHELL,                                     /* \shell */
-       META_SLEEP                                      /* \sleep */
+       META_SLEEP,                                     /* \sleep */
+       META_IF,                                        /* \if */
+       META_ELIF,                                      /* \elif */
+       META_ELSE,                                      /* \else */
+       META_ENDIF                                      /* \endif */
 } MetaCommand;
 
 typedef enum QueryMode
@@ -1645,6 +1655,7 @@ setBoolValue(PgBenchValue *pv, bool bval)
        pv->type = PGBT_BOOLEAN;
        pv->u.bval = bval;
 }
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -2377,6 +2388,14 @@ getMetaCommand(const char *cmd)
                mc = META_SHELL;
        else if (pg_strcasecmp(cmd, "sleep") == 0)
                mc = META_SLEEP;
+       else if (pg_strcasecmp(cmd, "if") == 0)
+               mc = META_IF;
+       else if (pg_strcasecmp(cmd, "elif") == 0)
+               mc = META_ELIF;
+       else if (pg_strcasecmp(cmd, "else") == 0)
+               mc = META_ELSE;
+       else if (pg_strcasecmp(cmd, "endif") == 0)
+               mc = META_ENDIF;
        else
                mc = META_NONE;
        return mc;
@@ -2498,11 +2517,11 @@ preparedStatementName(char *buffer, int file, int state)
 }
 
 static void
-commandFailed(CState *st, const char *message)
+commandFailed(CState *st, const char *cmd, const char *message)
 {
        fprintf(stderr,
-                       "client %d aborted in command %d of script %d; %s\n",
-                       st->id, st->command, st->use_file, message);
+                       "client %d aborted in command %d (%s) of script %d; 
%s\n",
+                       st->id, st->command, cmd, st->use_file, message);
 }
 
 /* return a script number with a weighted choice. */
@@ -2690,6 +2709,8 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                        st->state = CSTATE_START_THROTTLE;
                                else
                                        st->state = CSTATE_START_TX;
+                               /* check consistency */
+                               Assert(conditional_stack_empty(st->cstack));
                                break;
 
                                /*
@@ -2855,7 +2876,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                {
                                        if (!sendCommand(st, command))
                                        {
-                                               commandFailed(st, "SQL command 
send failed");
+                                               commandFailed(st, "SQL", "SQL 
command send failed");
                                                st->state = CSTATE_ABORTED;
                                        }
                                        else
@@ -2888,7 +2909,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 
                                                if (!evaluateSleep(st, argc, 
argv, &usec))
                                                {
-                                                       commandFailed(st, 
"execution of meta-command 'sleep' failed");
+                                                       commandFailed(st, 
"sleep", "execution of meta-command failed");
                                                        st->state = 
CSTATE_ABORTED;
                                                        break;
                                                }
@@ -2899,77 +2920,209 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                                st->state = CSTATE_SLEEP;
                                                break;
                                        }
-                                       else
+                                       else if (command->meta == META_SET ||
+                                                        command->meta == 
META_IF ||
+                                                        command->meta == 
META_ELIF)
                                        {
+                                               /* backslash commands with an 
expression to evaluate */
+                                               PgBenchExpr *expr = 
command->expr;
+                                               PgBenchValue result;
+
+                                               if (command->meta == META_ELIF 
&&
+                                                       
conditional_stack_peek(st->cstack) == IFSTATE_TRUE)
+                                               {
+                                                       /* elif after executed 
block, skip eval and wait for endif */
+                                                       
conditional_stack_poke(st->cstack, IFSTATE_IGNORED);
+                                                       goto 
move_to_end_command;
+                                               }
+
+                                               if (!evaluateExpr(thread, st, 
expr, &result))
+                                               {
+                                                       commandFailed(st, 
argv[0], "evaluation of meta-command failed");
+                                                       st->state = 
CSTATE_ABORTED;
+                                                       break;
+                                               }
+
                                                if (command->meta == META_SET)
                                                {
-                                                       PgBenchExpr *expr = 
command->expr;
-                                                       PgBenchValue result;
-
-                                                       if 
(!evaluateExpr(thread, st, expr, &result))
-                                                       {
-                                                               
commandFailed(st, "evaluation of meta-command 'set' failed");
-                                                               st->state = 
CSTATE_ABORTED;
-                                                               break;
-                                                       }
-
                                                        if 
(!putVariableValue(st, argv[0], argv[1], &result))
                                                        {
-                                                               
commandFailed(st, "assignment of meta-command 'set' failed");
+                                                               
commandFailed(st, "set", "assignment of meta-command failed");
                                                                st->state = 
CSTATE_ABORTED;
                                                                break;
                                                        }
                                                }
-                                               else if (command->meta == 
META_SETSHELL)
+                                               else /* if and elif evaluated 
cases */
                                                {
-                                                       bool            ret = 
runShellCommand(st, argv[1], argv + 2, argc - 2);
+                                                       bool cond = 
valueTruth(&result);
 
-                                                       if (timer_exceeded) /* 
timeout */
+                                                       /* execute or not 
depending on evaluated condition */
+                                                       if (command->meta == 
META_IF)
                                                        {
-                                                               st->state = 
CSTATE_FINISHED;
-                                                               break;
-                                                       }
-                                                       else if (!ret)  /* on 
error */
-                                                       {
-                                                               
commandFailed(st, "execution of meta-command 'setshell' failed");
-                                                               st->state = 
CSTATE_ABORTED;
-                                                               break;
+                                                               
conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
                                                        }
-                                                       else
+                                                       else /* elif */
                                                        {
-                                                               /* succeeded */
+                                                               /* we should 
get here only if the "elif" needed evaluation */
+                                                               
Assert(conditional_stack_peek(st->cstack) == IFSTATE_FALSE);
+                                                               
conditional_stack_poke(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
                                                        }
                                                }
-                                               else if (command->meta == 
META_SHELL)
+                                       }
+                                       else if (command->meta == META_ELSE)
+                                       {
+                                               switch 
(conditional_stack_peek(st->cstack))
+                                               {
+                                                       case IFSTATE_TRUE:
+                                                               
conditional_stack_poke(st->cstack, IFSTATE_ELSE_FALSE);
+                                                               break;
+                                                       case IFSTATE_FALSE: /* 
inconsistent if active */
+                                                       case IFSTATE_IGNORED: 
/* inconsistent if active */
+                                                       case IFSTATE_NONE: /* 
else without if */
+                                                       case IFSTATE_ELSE_TRUE: 
/* else after else */
+                                                       case 
IFSTATE_ELSE_FALSE: /* else after else */
+                                                       default:
+                                                               /* dead code if 
conditional check is ok */
+                                                               Assert(false);
+                                               }
+                                               goto move_to_end_command;
+                                       }
+                                       else if (command->meta == META_ENDIF)
+                                       {
+                                               
Assert(!conditional_stack_empty(st->cstack));
+                                               
conditional_stack_pop(st->cstack);
+                                               goto move_to_end_command;
+                                       }
+                                       else if (command->meta == META_SETSHELL)
+                                       {
+                                               bool            ret = 
runShellCommand(st, argv[1], argv + 2, argc - 2);
+
+                                               if (timer_exceeded) /* timeout 
*/
+                                               {
+                                                       st->state = 
CSTATE_FINISHED;
+                                                       break;
+                                               }
+                                               else if (!ret)  /* on error */
+                                               {
+                                                       commandFailed(st, 
"setshell", "execution of meta-command failed");
+                                                       st->state = 
CSTATE_ABORTED;
+                                                       break;
+                                               }
+                                               else
+                                               {
+                                                       /* succeeded */
+                                               }
+                                       }
+                                       else if (command->meta == META_SHELL)
+                                       {
+                                               bool            ret = 
runShellCommand(st, NULL, argv + 1, argc - 1);
+
+                                               if (timer_exceeded) /* timeout 
*/
+                                               {
+                                                       st->state = 
CSTATE_FINISHED;
+                                                       break;
+                                               }
+                                               else if (!ret)  /* on error */
                                                {
-                                                       bool            ret = 
runShellCommand(st, NULL, argv + 1, argc - 1);
+                                                       commandFailed(st, 
"shell", "execution of meta-command failed");
+                                                       st->state = 
CSTATE_ABORTED;
+                                                       break;
+                                               }
+                                               else
+                                               {
+                                                       /* succeeded */
+                                               }
+                                       }
+
+                                       move_to_end_command:
+                                       /*
+                                        * executing the expression or shell 
command might
+                                        * take a non-negligible amount of 
time, so reset
+                                        * 'now'
+                                        */
+                                       INSTR_TIME_SET_ZERO(now);
+
+                                       st->state = CSTATE_END_COMMAND;
+                               }
+                               break;
+
+                               /*
+                                * non executed conditional branch
+                                */
+                       case CSTATE_SKIP_COMMAND:
+                               Assert(!conditional_active(st->cstack));
+                               /* quickly skip commands until something to 
do... */
+                               while (true)
+                               {
+                                       command = 
sql_script[st->use_file].commands[st->command];
+
+                                       /* cannot reach end of script in that 
state */
+                                       Assert(command != NULL);
 
-                                                       if (timer_exceeded) /* 
timeout */
+                                       /* if this is conditional related, 
update conditional state */
+                                       if (command->type == META_COMMAND &&
+                                               (command->meta == META_IF ||
+                                                command->meta == META_ELIF ||
+                                                command->meta == META_ELSE ||
+                                                command->meta == META_ENDIF))
+                                       {
+                                               switch 
(conditional_stack_peek(st->cstack))
+                                               {
+                                               case IFSTATE_FALSE:
+                                                       if (command->meta == 
META_IF || command->meta == META_ELIF)
                                                        {
-                                                               st->state = 
CSTATE_FINISHED;
-                                                               break;
+                                                               /* we must 
evaluate the condition */
+                                                               st->state = 
CSTATE_START_COMMAND;
                                                        }
-                                                       else if (!ret)  /* on 
error */
+                                                       else if (command->meta 
== META_ELSE)
                                                        {
-                                                               
commandFailed(st, "execution of meta-command 'shell' failed");
-                                                               st->state = 
CSTATE_ABORTED;
-                                                               break;
+                                                               /* we must 
execute next command */
+                                                               
conditional_stack_poke(st->cstack, IFSTATE_ELSE_TRUE);
+                                                               st->state = 
CSTATE_START_COMMAND;
+                                                               st->command++;
                                                        }
-                                                       else
+                                                       else if (command->meta 
== META_ENDIF)
                                                        {
-                                                               /* succeeded */
+                                                               
Assert(!conditional_stack_empty(st->cstack));
+                                                               
conditional_stack_pop(st->cstack);
+                                                               if 
(conditional_active(st->cstack))
+                                                                       
st->state = CSTATE_START_COMMAND;
+                                                               /* else state 
remains in CSTATE_SKIP_COMMAND */
+                                                               st->command++;
                                                        }
-                                               }
+                                                       break;
 
-                                               /*
-                                                * executing the expression or 
shell command might
-                                                * take a non-negligible amount 
of time, so reset
-                                                * 'now'
-                                                */
-                                               INSTR_TIME_SET_ZERO(now);
+                                               case IFSTATE_IGNORED:
+                                               case IFSTATE_ELSE_FALSE:
+                                                       if (command->meta == 
META_IF)
+                                                               
conditional_stack_push(st->cstack, IFSTATE_IGNORED);
+                                                       else if (command->meta 
== META_ENDIF)
+                                                       {
+                                                               
Assert(!conditional_stack_empty(st->cstack));
+                                                               
conditional_stack_pop(st->cstack);
+                                                               if 
(conditional_active(st->cstack))
+                                                                       
st->state = CSTATE_START_COMMAND;
+                                                       }
+                                                       /* could detect "else" 
& "elif" after "else" */
+                                                       st->command++;
+                                                       break;
 
-                                               st->state = CSTATE_END_COMMAND;
+                                               case IFSTATE_NONE:
+                                               case IFSTATE_TRUE:
+                                               case IFSTATE_ELSE_TRUE:
+                                               default:
+                                                       /* inconsistent if 
inactive, unreachable dead code */
+                                                       Assert(false);
+                                               }
                                        }
+                                       else
+                                       {
+                                               /* skip and consider next */
+                                               st->command++;
+                                       }
+
+                                       if (st->state != CSTATE_SKIP_COMMAND)
+                                               break;
                                }
                                break;
 
@@ -2982,7 +3135,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                        fprintf(stderr, "client %d 
receiving\n", st->id);
                                if (!PQconsumeInput(st->con))
                                {                               /* there's 
something wrong */
-                                       commandFailed(st, "perhaps the backend 
died while processing");
+                                       commandFailed(st, "SQL", "perhaps the 
backend died while processing");
                                        st->state = CSTATE_ABORTED;
                                        break;
                                }
@@ -3004,7 +3157,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                                st->state = CSTATE_END_COMMAND;
                                                break;
                                        default:
-                                               commandFailed(st, 
PQerrorMessage(st->con));
+                                               commandFailed(st, "SQL", 
PQerrorMessage(st->con));
                                                PQclear(res);
                                                st->state = CSTATE_ABORTED;
                                                break;
@@ -3048,9 +3201,10 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                                                         
INSTR_TIME_GET_DOUBLE(st->stmt_begin));
                                }
 
-                               /* Go ahead with next command */
+                               /* Go ahead with next command, to be executed 
or skipped */
                                st->command++;
-                               st->state = CSTATE_START_COMMAND;
+                               st->state = conditional_active(st->cstack) ?
+                                       CSTATE_START_COMMAND : 
CSTATE_SKIP_COMMAND;
                                break;
 
                                /*
@@ -3061,6 +3215,13 @@ doCustom(TState *thread, CState *st, StatsData *agg)
                                /* transaction finished: calculate latency and 
do log */
                                processXactStats(thread, st, &now, false, agg);
 
+                               /* conditional stack must be empty */
+                               if (!conditional_stack_empty(st->cstack))
+                               {
+                                       fprintf(stderr, "end of script reached 
within a conditional, missing \\endif\n");
+                                       exit(1);
+                               }
+
                                if (is_connect)
                                {
                                        finishCon(st);
@@ -3870,19 +4031,25 @@ process_backslash_command(PsqlScanState sstate, const 
char *source)
        /* ... and convert it to enum form */
        my_command->meta = getMetaCommand(my_command->argv[0]);
 
-       if (my_command->meta == META_SET)
+       if (my_command->meta == META_SET ||
+               my_command->meta == META_IF ||
+               my_command->meta == META_ELIF)
        {
-               /* For \set, collect var name, then lex the expression. */
                yyscan_t        yyscanner;
 
-               if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
-                       syntax_error(source, lineno, my_command->line, 
my_command->argv[0],
-                                                "missing argument", NULL, -1);
+               /* For \set, collect var name */
+               if (my_command->meta == META_SET)
+               {
+                       if (!expr_lex_one_word(sstate, &word_buf, &word_offset))
+                               syntax_error(source, lineno, my_command->line, 
my_command->argv[0],
+                                                        "missing argument", 
NULL, -1);
 
-               offsets[j] = word_offset;
-               my_command->argv[j++] = pg_strdup(word_buf.data);
-               my_command->argc++;
+                       offsets[j] = word_offset;
+                       my_command->argv[j++] = pg_strdup(word_buf.data);
+                       my_command->argc++;
+               }
 
+               /* then for all parse the expression */
                yyscanner = expr_scanner_init(sstate, source, lineno, 
start_offset,
                                                                          
my_command->argv[0]);
 
@@ -3978,6 +4145,12 @@ process_backslash_command(PsqlScanState sstate, const 
char *source)
                        syntax_error(source, lineno, my_command->line, 
my_command->argv[0],
                                                 "missing command", NULL, -1);
        }
+       else if (my_command->meta == META_ELSE || my_command->meta == 
META_ENDIF)
+       {
+               if (my_command->argc != 1)
+                       syntax_error(source, lineno, my_command->line, 
my_command->argv[0],
+                                                "unexpected argument", NULL, 
-1);
+       }
        else
        {
                /* my_command->meta == META_NONE */
@@ -3990,6 +4163,62 @@ process_backslash_command(PsqlScanState sstate, const 
char *source)
        return my_command;
 }
 
+static void
+ConditionError(const char *desc, int cmdn, const char *msg)
+{
+       fprintf(stderr,
+                       "condition error in script \"%s\" command %d: %s\n",
+                       desc, cmdn, msg);
+       exit(1);
+}
+
+/*
+ * Partial evaluation of conditionals before recording and running the script.
+ */
+static void
+CheckConditional(ParsedScript ps)
+{
+       /* statically check conditional structure */
+       ConditionalStack cs = conditional_stack_create();
+       int i;
+       for (i = 0 ; ps.commands[i] != NULL ; i++)
+       {
+               Command *cmd = ps.commands[i];
+               if (cmd->type == META_COMMAND)
+               {
+                       switch (cmd->meta)
+                       {
+                       case META_IF:
+                               conditional_stack_push(cs, IFSTATE_FALSE);
+                               break;
+                       case META_ELIF:
+                               if (conditional_stack_empty(cs))
+                                       ConditionError(ps.desc, i+1, "\\elif 
without matching \\if");
+                               if (conditional_stack_peek(cs) == 
IFSTATE_ELSE_FALSE)
+                                       ConditionError(ps.desc, i+1, "\\elif 
after \\else");
+                               break;
+                       case META_ELSE:
+                               if (conditional_stack_empty(cs))
+                                       ConditionError(ps.desc, i+1, "\\else 
without matching \\if");
+                               if (conditional_stack_peek(cs) == 
IFSTATE_ELSE_FALSE)
+                                       ConditionError(ps.desc, i+1, "\\else 
after \\else");
+                               conditional_stack_poke(cs, IFSTATE_ELSE_FALSE);
+                               break;
+                       case META_ENDIF:
+                               if (!conditional_stack_pop(cs))
+                                       ConditionError(ps.desc, i+1, "\\endif 
without matching \\if");
+                               break;
+                       default:
+                               /* ignore anything else... */
+                               break;
+                       }
+               }
+       }
+       if (!conditional_stack_empty(cs))
+               ConditionError(ps.desc, i+1, "\\if without matching \\endif");
+       conditional_stack_destroy(cs);
+}
+
 /*
  * Parse a script (either the contents of a file, or a built-in script)
  * and add it to the list of scripts.
@@ -4275,6 +4504,8 @@ addScript(ParsedScript script)
                exit(1);
        }
 
+       CheckConditional(script);
+
        sql_script[num_scripts] = script;
        num_scripts++;
 }
@@ -5021,6 +5252,12 @@ main(int argc, char **argv)
                }
        }
 
+       /* other CState initializations */
+       for (i = 0; i < nclients; i++)
+       {
+               state[i].cstack = conditional_stack_create();
+       }
+
        if (debug)
        {
                if (duration <= 0)
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl 
b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 50cbb23..7448a96 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -264,6 +264,12 @@ pgbench(
                qr{command=51.: int -7793829335365542153\b},
                qr{command=52.: int -?\d+\b},
                qr{command=53.: boolean true\b},
+               qr{command=65.: int 65\b},
+               qr{command=74.: int 74\b},
+               qr{command=83.: int 83\b},
+               qr{command=86.: int 86\b},
+               qr{command=93.: int 93\b},
+               qr{command=95.: int 0\b},
        ],
        'pgbench expressions',
        {   '001_pgbench_expressions' => q{-- integer functions
@@ -349,6 +355,41 @@ pgbench(
 \set v2 5432
 \set v3 -54.21E-2
 SELECT :v0, :v1, :v2, :v3;
+-- if tests
+\set nope 0
+\if 1 > 0
+\set id debug(65)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 > 0
+\set ie debug(74)
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 < 0
+\set nope 1
+\else
+\set if debug(83)
+\endif
+\if 1 = 1
+\set ig debug(86)
+\elif 0
+\set nope 1
+\endif
+\if 1 = 0
+\set nope 1
+\elif 1 <> 0
+\set ih debug(93)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 # backslash commands
@@ -396,7 +437,7 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 
        # SHELL
        [   'shell bad command',               0,
-               [qr{meta-command 'shell' failed}], q{\shell no-such-command} ],
+               [qr{\(shell\) .* meta-command failed}], q{\shell 
no-such-command} ],
        [   'shell undefined variable', 0,
                [qr{undefined variable ":nosuchvariable"}],
                q{-- undefined variable in shell
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl 
b/src/bin/pgbench/t/002_pgbench_no_server.pl
index 6ea55f8..80c5aed 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -8,6 +8,16 @@ use warnings;
 use TestLib;
 use Test::More;
 
+# create a directory for scripts
+my $testname = $0;
+$testname =~ s,.*/,,;
+$testname =~ s/\.pl$//;
+
+my $testdir = "$TestLib::tmp_check/t_${testname}_stuff";
+mkdir $testdir
+       or
+       BAIL_OUT("could not create test directory \"${testdir}\": $!");
+
 # invoke pgbench
 sub pgbench
 {
@@ -17,6 +27,28 @@ sub pgbench
                $stat, $out, $err, $name);
 }
 
+# invoke pgbench with scripts
+sub pgbench_scripts
+{
+       my ($opts, $stat, $out, $err, $name, $files) = @_;
+       my @cmd = ('pgbench', split /\s+/, $opts);
+       my @filenames = ();
+       if (defined $files)
+       {
+               for my $fn (sort keys %$files)
+               {
+                       my $filename = $testdir . '/' . $fn;
+                       # cleanup file weight if any
+                       $filename =~ s/\@\d+$//;
+                       # cleanup from prior runs
+                       unlink $filename;
+                       append_to_file($filename, $$files{$fn});
+                       push @cmd, '-f', $filename;
+               }
+       }
+       command_checks_all(\@cmd, $stat, $out, $err, $name);
+}
+
 #
 # Option various errors
 #
@@ -125,4 +157,24 @@ pgbench(
                qr{simple-update},              qr{select-only} ],
        'pgbench builtin list');
 
+my @script_tests = (
+       # name, err, { file => contents }
+       [ 'missing endif', [qr{\\if without matching \\endif}], 
{'if-noendif.sql' => '\if 1'} ],
+       [ 'missing if on elif', [qr{\\elif without matching \\if}], 
{'elif-noif.sql' => '\elif 1'} ],
+       [ 'missing if on else', [qr{\\else without matching \\if}], 
{'else-noif.sql' => '\else'} ],
+       [ 'missing if on endif', [qr{\\endif without matching \\if}], 
{'endif-noif.sql' => '\endif'} ],
+       [ 'elif after else', [qr{\\elif after \\else}], {'else-elif.sql' => 
"\\if 1\n\\else\n\\elif 0\n\\endif"} ],
+       [ 'else after else', [qr{\\else after \\else}], {'else-else.sql' => 
"\\if 1\n\\else\n\\else\n\\endif"} ],
+       [ 'if syntax error', [qr{syntax error in command "if"}], {'if-bad.sql' 
=> "\\if\n\\endif\n"} ],
+       [ 'elif syntax error', [qr{syntax error in command "elif"}], 
{'elif-bad.sql' => "\\if 0\n\\elif +\n\\endif\n"} ],
+       [ 'else syntax error', [qr{unexpected argument in command "else"}], 
{'else-bad.sql' => "\\if 0\n\\else BAD\n\\endif\n"} ],
+       [ 'endif syntax error', [qr{unexpected argument in command "endif"}], 
{'endif-bad.sql' => "\\if 0\n\\endif BAD\n"} ],
+);
+
+for my $t (@script_tests)
+{
+       my ($name, $err, $files) = @$t;
+       pgbench_scripts('', 1, [qr{^$}], $err, 'pgbench option error: ' . 
$name, $files);
+}
+
 done_testing();
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index c8eb2f9..b3166ec 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -21,7 +21,7 @@ REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
 override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
 override LDFLAGS := -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport) 
$(LDFLAGS)
 
-OBJS=  command.o common.o conditional.o copy.o crosstabview.o \
+OBJS=  command.o common.o copy.o crosstabview.o \
        describe.o help.o input.o large_obj.o mainloop.o \
        prompt.o psqlscanslash.o sql_help.o startup.o stringutils.o \
        tab-complete.o variables.o \
diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h
index 95ad02d..29a8edd 100644
--- a/src/bin/psql/command.h
+++ b/src/bin/psql/command.h
@@ -10,7 +10,7 @@
 
 #include "fe_utils/print.h"
 #include "fe_utils/psqlscan.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
 
 
 typedef enum _backslashResult
diff --git a/src/bin/psql/conditional.c b/src/bin/psql/conditional.c
deleted file mode 100644
index cebf8c7..0000000
--- a/src/bin/psql/conditional.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2018, PostgreSQL Global Development Group
- *
- * src/bin/psql/conditional.c
- */
-#include "postgres_fe.h"
-
-#include "conditional.h"
-
-/*
- * create stack
- */
-ConditionalStack
-conditional_stack_create(void)
-{
-       ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
-
-       cstack->head = NULL;
-       return cstack;
-}
-
-/*
- * destroy stack
- */
-void
-conditional_stack_destroy(ConditionalStack cstack)
-{
-       while (conditional_stack_pop(cstack))
-               continue;
-       free(cstack);
-}
-
-/*
- * Create a new conditional branch.
- */
-void
-conditional_stack_push(ConditionalStack cstack, ifState new_state)
-{
-       IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));
-
-       p->if_state = new_state;
-       p->query_len = -1;
-       p->paren_depth = -1;
-       p->next = cstack->head;
-       cstack->head = p;
-}
-
-/*
- * Destroy the topmost conditional branch.
- * Returns false if there was no branch to end.
- */
-bool
-conditional_stack_pop(ConditionalStack cstack)
-{
-       IfStackElem *p = cstack->head;
-
-       if (!p)
-               return false;
-       cstack->head = cstack->head->next;
-       free(p);
-       return true;
-}
-
-/*
- * Fetch the current state of the top of the stack.
- */
-ifState
-conditional_stack_peek(ConditionalStack cstack)
-{
-       if (conditional_stack_empty(cstack))
-               return IFSTATE_NONE;
-       return cstack->head->if_state;
-}
-
-/*
- * Change the state of the topmost branch.
- * Returns false if there was no branch state to set.
- */
-bool
-conditional_stack_poke(ConditionalStack cstack, ifState new_state)
-{
-       if (conditional_stack_empty(cstack))
-               return false;
-       cstack->head->if_state = new_state;
-       return true;
-}
-
-/*
- * True if there are no active \if-blocks.
- */
-bool
-conditional_stack_empty(ConditionalStack cstack)
-{
-       return cstack->head == NULL;
-}
-
-/*
- * True if we should execute commands normally; that is, the current
- * conditional branch is active, or there is no open \if block.
- */
-bool
-conditional_active(ConditionalStack cstack)
-{
-       ifState         s = conditional_stack_peek(cstack);
-
-       return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
-}
-
-/*
- * Save current query buffer length in topmost stack entry.
- */
-void
-conditional_stack_set_query_len(ConditionalStack cstack, int len)
-{
-       Assert(!conditional_stack_empty(cstack));
-       cstack->head->query_len = len;
-}
-
-/*
- * Fetch last-recorded query buffer length from topmost stack entry.
- * Will return -1 if no stack or it was never saved.
- */
-int
-conditional_stack_get_query_len(ConditionalStack cstack)
-{
-       if (conditional_stack_empty(cstack))
-               return -1;
-       return cstack->head->query_len;
-}
-
-/*
- * Save current parenthesis nesting depth in topmost stack entry.
- */
-void
-conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
-{
-       Assert(!conditional_stack_empty(cstack));
-       cstack->head->paren_depth = depth;
-}
-
-/*
- * Fetch last-recorded parenthesis nesting depth from topmost stack entry.
- * Will return -1 if no stack or it was never saved.
- */
-int
-conditional_stack_get_paren_depth(ConditionalStack cstack)
-{
-       if (conditional_stack_empty(cstack))
-               return -1;
-       return cstack->head->paren_depth;
-}
diff --git a/src/bin/psql/conditional.h b/src/bin/psql/conditional.h
deleted file mode 100644
index 565875a..0000000
--- a/src/bin/psql/conditional.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2018, PostgreSQL Global Development Group
- *
- * src/bin/psql/conditional.h
- */
-#ifndef CONDITIONAL_H
-#define CONDITIONAL_H
-
-/*
- * Possible states of a single level of \if block.
- */
-typedef enum ifState
-{
-       IFSTATE_NONE = 0,                       /* not currently in an \if 
block */
-       IFSTATE_TRUE,                           /* currently in an \if or \elif 
that is true
-                                                                * and all 
parent branches (if any) are true */
-       IFSTATE_FALSE,                          /* currently in an \if or \elif 
that is false
-                                                                * but no true 
branch has yet been seen, and
-                                                                * all parent 
branches (if any) are true */
-       IFSTATE_IGNORED,                        /* currently in an \elif that 
follows a true
-                                                                * branch, or 
the whole \if is a child of a
-                                                                * false parent 
branch */
-       IFSTATE_ELSE_TRUE,                      /* currently in an \else that 
is true and all
-                                                                * parent 
branches (if any) are true */
-       IFSTATE_ELSE_FALSE                      /* currently in an \else that 
is false or
-                                                                * ignored */
-} ifState;
-
-/*
- * The state of nested \ifs is stored in a stack.
- *
- * query_len is used to determine what accumulated text to throw away at the
- * end of an inactive branch.  (We could, perhaps, teach the lexer to not add
- * stuff to the query buffer in the first place when inside an inactive branch;
- * but that would be very invasive.)  We also need to save and restore the
- * lexer's parenthesis nesting depth when throwing away text.  (We don't need
- * to save and restore any of its other state, such as comment nesting depth,
- * because a backslash command could never appear inside a comment or SQL
- * literal.)
- */
-typedef struct IfStackElem
-{
-       ifState         if_state;               /* current state, see enum 
above */
-       int                     query_len;              /* length of query_buf 
at last branch start */
-       int                     paren_depth;    /* parenthesis depth at last 
branch start */
-       struct IfStackElem *next;       /* next surrounding \if, if any */
-} IfStackElem;
-
-typedef struct ConditionalStackData
-{
-       IfStackElem *head;
-}                      ConditionalStackData;
-
-typedef struct ConditionalStackData *ConditionalStack;
-
-
-extern ConditionalStack conditional_stack_create(void);
-
-extern void conditional_stack_destroy(ConditionalStack cstack);
-
-extern void conditional_stack_push(ConditionalStack cstack, ifState new_state);
-
-extern bool conditional_stack_pop(ConditionalStack cstack);
-
-extern ifState conditional_stack_peek(ConditionalStack cstack);
-
-extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state);
-
-extern bool conditional_stack_empty(ConditionalStack cstack);
-
-extern bool conditional_active(ConditionalStack cstack);
-
-extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
-
-extern int     conditional_stack_get_query_len(ConditionalStack cstack);
-
-extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int 
depth);
-
-extern int     conditional_stack_get_paren_depth(ConditionalStack cstack);
-
-#endif                                                 /* CONDITIONAL_H */
diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h
index 2354b8f..3a84565 100644
--- a/src/bin/psql/prompt.h
+++ b/src/bin/psql/prompt.h
@@ -10,7 +10,7 @@
 
 /* enum promptStatus_t is now defined by psqlscan.h */
 #include "fe_utils/psqlscan.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
 
 char      *get_prompt(promptStatus_t status, ConditionalStack cstack);
 
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index 5e0bd0e..34df35e 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -19,7 +19,7 @@
 #include "postgres_fe.h"
 
 #include "psqlscanslash.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
 
 #include "libpq-fe.h"
 }
diff --git a/src/fe_utils/Makefile b/src/fe_utils/Makefile
index 3f4ba8b..5362cff 100644
--- a/src/fe_utils/Makefile
+++ b/src/fe_utils/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
 
 override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
 
-OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o
+OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o conditional.o
 
 all: libpgfeutils.a
 
diff --git a/src/fe_utils/conditional.c b/src/fe_utils/conditional.c
new file mode 100644
index 0000000..e575a9c
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,176 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/fe_utils/conditional.c
+ *
+ * A stack of automaton states to handle nested conditionals.
+ */
+#include "postgres_fe.h"
+
+#include "fe_utils/conditional.h"
+
+/*
+ * create stack
+ */
+ConditionalStack
+conditional_stack_create(void)
+{
+       ConditionalStack cstack = pg_malloc(sizeof(ConditionalStackData));
+
+       cstack->head = NULL;
+       return cstack;
+}
+
+/*
+ * destroy stack
+ */
+void
+conditional_stack_destroy(ConditionalStack cstack)
+{
+       while (conditional_stack_pop(cstack))
+               continue;
+       free(cstack);
+}
+
+/*
+ * Create a new conditional branch.
+ */
+void
+conditional_stack_push(ConditionalStack cstack, ifState new_state)
+{
+       IfStackElem *p = (IfStackElem *) pg_malloc(sizeof(IfStackElem));
+
+       p->if_state = new_state;
+       p->query_len = -1;
+       p->paren_depth = -1;
+       p->next = cstack->head;
+       cstack->head = p;
+}
+
+/*
+ * Destroy the topmost conditional branch.
+ * Returns false if there was no branch to end.
+ */
+bool
+conditional_stack_pop(ConditionalStack cstack)
+{
+       IfStackElem *p = cstack->head;
+
+       if (!p)
+               return false;
+       cstack->head = cstack->head->next;
+       free(p);
+       return true;
+}
+
+/*
+ * Returns current stack depth, for debugging purposes.
+ */
+int
+conditional_stack_depth(ConditionalStack cstack)
+{
+       if (cstack == NULL)
+               return -1;
+       else
+       {
+               IfStackElem     *p = cstack->head;
+               int                     depth = 0;
+               while (p != NULL)
+               {
+                       depth++;
+                       p = p->next;
+               }
+               return depth;
+       }
+}
+
+/*
+ * Fetch the current state of the top of the stack.
+ */
+ifState
+conditional_stack_peek(ConditionalStack cstack)
+{
+       if (conditional_stack_empty(cstack))
+               return IFSTATE_NONE;
+       return cstack->head->if_state;
+}
+
+/*
+ * Change the state of the topmost branch.
+ * Returns false if there was no branch state to set.
+ */
+bool
+conditional_stack_poke(ConditionalStack cstack, ifState new_state)
+{
+       if (conditional_stack_empty(cstack))
+               return false;
+       cstack->head->if_state = new_state;
+       return true;
+}
+
+/*
+ * True if there are no active \if-blocks.
+ */
+bool
+conditional_stack_empty(ConditionalStack cstack)
+{
+       return cstack->head == NULL;
+}
+
+/*
+ * True if we should execute commands normally; that is, the current
+ * conditional branch is active, or there is no open \if block.
+ */
+bool
+conditional_active(ConditionalStack cstack)
+{
+       ifState         s = conditional_stack_peek(cstack);
+
+       return s == IFSTATE_NONE || s == IFSTATE_TRUE || s == IFSTATE_ELSE_TRUE;
+}
+
+/*
+ * Save current query buffer length in topmost stack entry.
+ */
+void
+conditional_stack_set_query_len(ConditionalStack cstack, int len)
+{
+       Assert(!conditional_stack_empty(cstack));
+       cstack->head->query_len = len;
+}
+
+/*
+ * Fetch last-recorded query buffer length from topmost stack entry.
+ * Will return -1 if no stack or it was never saved.
+ */
+int
+conditional_stack_get_query_len(ConditionalStack cstack)
+{
+       if (conditional_stack_empty(cstack))
+               return -1;
+       return cstack->head->query_len;
+}
+
+/*
+ * Save current parenthesis nesting depth in topmost stack entry.
+ */
+void
+conditional_stack_set_paren_depth(ConditionalStack cstack, int depth)
+{
+       Assert(!conditional_stack_empty(cstack));
+       cstack->head->paren_depth = depth;
+}
+
+/*
+ * Fetch last-recorded parenthesis nesting depth from topmost stack entry.
+ * Will return -1 if no stack or it was never saved.
+ */
+int
+conditional_stack_get_paren_depth(ConditionalStack cstack)
+{
+       if (conditional_stack_empty(cstack))
+               return -1;
+       return cstack->head->paren_depth;
+}
diff --git a/src/include/fe_utils/conditional.h 
b/src/include/fe_utils/conditional.h
new file mode 100644
index 0000000..20e6a46
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,98 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/include/fe_utils/conditional.h
+ *
+ * This file describes a stack of automaton states which
+ * allow a manage nested conditionals.
+ *
+ * It is used by:
+ * - "psql" interpretor for handling \if ... \endif
+ * - "pgbench" interpretor for handling \if ... \endif
+ * - "pgbench" syntax checker to test for proper nesting
+ *
+ * The stack holds the state of enclosing conditionals (are we in
+ * a true branch? in a false branch? have we already encountered
+ * a true branch?) so that the interpreter knows whether to execute
+ * code and whether to evaluate conditions.
+ */
+#ifndef CONDITIONAL_H
+#define CONDITIONAL_H
+
+/*
+ * Possible states of a single level of \if block.
+ */
+typedef enum ifState
+{
+       IFSTATE_NONE = 0,                       /* not currently in an \if 
block */
+       IFSTATE_TRUE,                           /* currently in an \if or \elif 
that is true
+                                                                * and all 
parent branches (if any) are true */
+       IFSTATE_FALSE,                          /* currently in an \if or \elif 
that is false
+                                                                * but no true 
branch has yet been seen, and
+                                                                * all parent 
branches (if any) are true */
+       IFSTATE_IGNORED,                        /* currently in an \elif that 
follows a true
+                                                                * branch, or 
the whole \if is a child of a
+                                                                * false parent 
branch */
+       IFSTATE_ELSE_TRUE,                      /* currently in an \else that 
is true and all
+                                                                * parent 
branches (if any) are true */
+       IFSTATE_ELSE_FALSE                      /* currently in an \else that 
is false or
+                                                                * ignored */
+} ifState;
+
+/*
+ * The state of nested \ifs is stored in a stack.
+ *
+ * query_len is used to determine what accumulated text to throw away at the
+ * end of an inactive branch.  (We could, perhaps, teach the lexer to not add
+ * stuff to the query buffer in the first place when inside an inactive branch;
+ * but that would be very invasive.)  We also need to save and restore the
+ * lexer's parenthesis nesting depth when throwing away text.  (We don't need
+ * to save and restore any of its other state, such as comment nesting depth,
+ * because a backslash command could never appear inside a comment or SQL
+ * literal.)
+ */
+typedef struct IfStackElem
+{
+       ifState         if_state;               /* current state, see enum 
above */
+       int                     query_len;              /* length of query_buf 
at last branch start */
+       int                     paren_depth;    /* parenthesis depth at last 
branch start */
+       struct IfStackElem *next;       /* next surrounding \if, if any */
+} IfStackElem;
+
+typedef struct ConditionalStackData
+{
+       IfStackElem *head;
+}                      ConditionalStackData;
+
+typedef struct ConditionalStackData *ConditionalStack;
+
+
+extern ConditionalStack conditional_stack_create(void);
+
+extern void conditional_stack_destroy(ConditionalStack cstack);
+
+extern int conditional_stack_depth(ConditionalStack cstack);
+
+extern void conditional_stack_push(ConditionalStack cstack, ifState new_state);
+
+extern bool conditional_stack_pop(ConditionalStack cstack);
+
+extern ifState conditional_stack_peek(ConditionalStack cstack);
+
+extern bool conditional_stack_poke(ConditionalStack cstack, ifState new_state);
+
+extern bool conditional_stack_empty(ConditionalStack cstack);
+
+extern bool conditional_active(ConditionalStack cstack);
+
+extern void conditional_stack_set_query_len(ConditionalStack cstack, int len);
+
+extern int     conditional_stack_get_query_len(ConditionalStack cstack);
+
+extern void conditional_stack_set_paren_depth(ConditionalStack cstack, int 
depth);
+
+extern int     conditional_stack_get_paren_depth(ConditionalStack cstack);
+
+#endif                                                 /* CONDITIONAL_H */

Reply via email to