Add \if support to pgbench
authorTeodor Sigaev <teodor@sigaev.ru>
Thu, 22 Mar 2018 14:42:03 +0000 (17:42 +0300)
committerTeodor Sigaev <teodor@sigaev.ru>
Thu, 22 Mar 2018 14:42:03 +0000 (17:42 +0300)
Patch adds \if to pgbench as it done for psql. Implementation shares condition
stack code with psql, so, this code is moved to fe_utils directory.

Author: Fabien COELHO with minor editorization by me
Review by: Vik Fearing, Fedor Sigaev
Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.20.1711252200190.28523@lancre

12 files changed:
doc/src/sgml/ref/pgbench.sgml
doc/src/sgml/ref/psql-ref.sgml
src/bin/pgbench/pgbench.c
src/bin/pgbench/t/001_pgbench_with_server.pl
src/bin/pgbench/t/002_pgbench_no_server.pl
src/bin/psql/Makefile
src/bin/psql/command.h
src/bin/psql/prompt.h
src/bin/psql/psqlscanslash.l
src/fe_utils/Makefile
src/fe_utils/conditional.c [moved from src/bin/psql/conditional.c with 83% similarity]
src/include/fe_utils/conditional.h [moved from src/bin/psql/conditional.h with 76% similarity]

index f07ddf1226e842ca1ddc6967318c9b7f5ee0d323..d52d324bf0ddd5eddcb48b1230fafebc8235e54f 100644 (file)
@@ -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>
index bfdf859731145e58de9956dfb56ec881bd9a473a..10b97950ec14eda02ad6b6360114e26c00a36ec8 100644 (file)
@@ -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>
index a15aa06b194a592a960db0861c24c750e7bb0c19..894571e54f2327926be9ae762e241c6e30d5dd5e 100644 (file)
@@ -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)
                    {
-                       if (command->meta == META_SET)
+                       /* 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)
                        {
-                           PgBenchExpr *expr = command->expr;
-                           PgBenchValue result;
+                           /* 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, "evaluation of meta-command 'set' failed");
-                               st->state = CSTATE_ABORTED;
-                               break;
-                           }
+                       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)
+                       {
                            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;
+                               conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE);
                            }
-                           else if (!ret)  /* on error */
+                           else /* elif */
                            {
-                               commandFailed(st, "execution of meta-command 'setshell' failed");
-                               st->state = CSTATE_ABORTED;
-                               break;
-                           }
-                           else
-                           {
-                               /* 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 */
                        {
-                           bool        ret = runShellCommand(st, NULL, argv + 1, argc - 1);
+                           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 */
+                       {
+                           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];
 
-                           if (timer_exceeded) /* timeout */
+                   /* cannot reach end of script in that state */
+                   Assert(command != NULL);
+
+                   /* 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)
index 50cbb23f20bd060d1c6cfbbbb1058450aa2a0f36..7448a9615019f9f6b9551d90516163c766136c6a 100644 (file)
@@ -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
index 6ea55f8daea59a0d9ea664189c7c6facc6e4e13a..80c5aed435186e5fbbe9df4a3462effd5fa00350 100644 (file)
@@ -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();
index c8eb2f95ccfa2aed309aceabccab5412bae9b43e..b3166ecd158a3b3e1529da6acf46289ed2b819d3 100644 (file)
@@ -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 \
index 95ad02dfe23c0308314eaf0ce07ca4183688ae5c..29a8edd5a5ddeeafdab4a10a73e5f9bd0ccb5fc5 100644 (file)
@@ -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
index 2354b8f8c7a9186c16e2e0a3b497a668e1b2b7a2..3a84565e4b8840efaa7d871fa5ca5c13dc9f3751 100644 (file)
@@ -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);
 
index 5e0bd0e774900511c1147bbd59e2ec8aa3b0d06d..34df35e5f43ea05e92009e296511cb2742af4d48 100644 (file)
@@ -19,7 +19,7 @@
 #include "postgres_fe.h"
 
 #include "psqlscanslash.h"
-#include "conditional.h"
+#include "fe_utils/conditional.h"
 
 #include "libpq-fe.h"
 }
index 3f4ba8bf7c9990c57787488f76d86c3bfade03f8..5362cffd5736fd473f7d834ee40ef09529266345 100644 (file)
@@ -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
 
similarity index 83%
rename from src/bin/psql/conditional.c
rename to src/fe_utils/conditional.c
index cebf8c766c92deca6c94472bd39df0a800d1a9cf..0af80521ce14f821f6b1d739c0a930af7ed6db42 100644 (file)
@@ -1,13 +1,15 @@
-/*
- * psql - the PostgreSQL interactive terminal
+/*-------------------------------------------------------------------------
+ * A stack of automaton states to handle nested conditionals.
  *
  * Copyright (c) 2000-2018, PostgreSQL Global Development Group
  *
- * src/bin/psql/conditional.c
+ * src/fe_utils/conditional.c
+ *
+ *-------------------------------------------------------------------------
  */
 #include "postgres_fe.h"
 
-#include "conditional.h"
+#include "fe_utils/conditional.h"
 
 /*
  * create stack
@@ -63,6 +65,27 @@ conditional_stack_pop(ConditionalStack cstack)
    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.
  */
similarity index 76%
rename from src/bin/psql/conditional.h
rename to src/include/fe_utils/conditional.h
index 565875ac312168f67ea1fe4c32711973648d6831..15162071976f7ca8040b1b99d726afafdcf23ac0 100644 (file)
@@ -1,9 +1,24 @@
-/*
- * psql - the PostgreSQL interactive terminal
+/*-------------------------------------------------------------------------
+ * A stack of automaton states to handle nested conditionals.
+ *
+ * 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.
  *
  * Copyright (c) 2000-2018, PostgreSQL Global Development Group
  *
- * src/bin/psql/conditional.h
+ * src/include/fe_utils/conditional.h
+ *
+ *-------------------------------------------------------------------------
  */
 #ifndef CONDITIONAL_H
 #define CONDITIONAL_H
@@ -60,6 +75,8 @@ 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);