pgbench - add \if support

Started by Fabien COELHOabout 8 years ago29 messages
#1Fabien COELHO
coelho@cri.ensmp.fr
1 attachment(s)

This patch adds \if support to pgbench, similar to psql's version added
in March.

This patch brings a consistent set of features especially when combined
with two other patches already in the (slow) CF process:

- https://commitfest.postgresql.org/10/596/ .. /15/985/
adds support for booleans expressions (comparisons, logical
operators, ...). This enhanced expression engine would be useful
to allow client-side expression in psql.

- https://commitfest.postgresql.org/10/669/ .. /15/669/
adds support for \gset, so that pgbench can interact with a database
and extract something into a variable, instead of discarding it.

This patch adds a \if construct so that an expression on variables,
possibly with data coming from the database, can change the behavior of a
script.

For instance, the following script, which uses features from the three
patches, would implement TPC-B per spec (not "tpcb-like", but really as
specified).

\set tbid random(1, :scale)
\set tid 10 * (:tbid - 1) + random(1, 10)
-- client in same branch as teller at 85%
\if :scale = 1 OR random(0, 99) < 85
-- same branch
\set bid :tbid
\else
-- different branch
\set bid 1 + (:tbid + random(1, :scale - 1)) % :scale
\endif
\set aid :bid * 100000 + random(1, 100000)
\set delta random(-999999, 999999)
BEGIN;
UPDATE pgbench_accounts
SET abalance = abalance + :delta WHERE aid = :aid
RETURNING abalance AS balance \gset
UPDATE pgbench_tellers
SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches
SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime)
VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;

The patch moves the conditional stack infrastructure from psql to
fe_utils, so that it is available to both psql & pgbench.

A partial evaluation is performed to detect structural errors (eg missing
endif, else after else...) when the script is parsed, so that such errors
cannot occur when a script is running.

A new automaton state is added to quickly step over false branches.

TAP tests ensure reasonable coverage of the feature.

--
Fabien

Attachments:

pgbench-if-1.patchtext/x-diff; name=pgbench-if-1.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 94b495e..715d096 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 bd96eae..9d1f90b 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"
@@ -270,6 +271,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.
 	 *
@@ -279,6 +283,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,
@@ -308,6 +313,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 */
@@ -365,7 +371,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
@@ -1304,6 +1314,26 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+/*
+ * Return true or false for conditional purposes.
+ * Non zero numerical values are true.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_INT:
+			return pval->u.ival != 0;
+		case PGBT_DOUBLE:
+			return pval->u.dval != 0.0;
+		default:
+			/* internal error, unexpected type */
+			Assert(0);
+			return false;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1748,6 +1778,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;
@@ -1869,11 +1907,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. */
@@ -2061,6 +2099,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;
 
 				/*
@@ -2226,7 +2266,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
@@ -2259,7 +2299,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;
 						}
@@ -2270,77 +2310,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 (!putVariableNumber(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;
 
@@ -2353,7 +2525,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;
 				}
@@ -2375,7 +2547,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;
@@ -2419,9 +2591,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;
 
 				/*
@@ -2432,6 +2605,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)
 				{
 					PQfinish(st->con);
@@ -3252,19 +3432,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]);
 
@@ -3360,6 +3546,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 */
@@ -3372,6 +3564,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(const char *desc, 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(desc, i+1, "\\elif without matching \\if");
+				if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE)
+					ConditionError(desc, i+1, "\\elif after \\else");
+				break;
+			case META_ELSE:
+				if (conditional_stack_empty(cs))
+					ConditionError(desc, i+1, "\\else without matching \\if");
+				if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE)
+					ConditionError(desc, i+1, "\\else after \\else");
+				conditional_stack_poke(cs, IFSTATE_ELSE_FALSE);
+				break;
+			case META_ENDIF:
+				if (!conditional_stack_pop(cs))
+					ConditionError(desc, i+1, "\\endif without matching \\if");
+				break;
+			default:
+				/* ignore anything else... */
+				break;
+			}
+		}
+	}
+	if (!conditional_stack_empty(cs))
+		ConditionError(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.
@@ -3462,6 +3710,7 @@ ParseScript(const char *script, const char *desc, int weight)
 
 	ps.commands[index] = NULL;
 
+	CheckConditional(desc, ps);
 	addScript(ps);
 
 	termPQExpBuffer(&line_buf);
@@ -4392,6 +4641,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 c095881..a725fb4 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -231,7 +231,14 @@ pgbench(
 		qr{command=18.: double 18\b},
 		qr{command=19.: double 19\b},
 		qr{command=20.: double 20\b},
-		qr{command=21.: int 9223372036854775807\b}, ],
+		qr{command=21.: int 9223372036854775807\b},
+		qr{command=25.: int 25\b},
+		qr{command=34.: int 34\b},
+		qr{command=43.: int 43\b},
+		qr{command=46.: int 46\b},
+		qr{command=53.: int 53\b},
+		qr{command=55.: int 0\b},
+		],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
 \set i1 debug(random(1, 100))
@@ -261,6 +268,41 @@ pgbench(
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
+-- if tests
+\set nope 0
+\if 1
+\set id debug(25)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ie debug(34)
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 0
+\set nope 1
+\else
+\set if debug(43)
+\endif
+\if 1
+\set ig debug(46)
+\elif 0
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ih debug(53)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 # backslash commands
@@ -308,7 +350,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 cabfe15..1490c33 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 7aedd0d..1d19849 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 63977ce..0000000
--- a/src/bin/psql/conditional.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2017, 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 0957627..0000000
--- a/src/bin/psql/conditional.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2017, 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 a7a95ef..0972e50 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 e3cde04..074b530 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 ebce38c..4d31819 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..19e04fe
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..f7eb0b6
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, 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 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 */
#2Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#1)
1 attachment(s)
Re: pgbench - add \if support

Mostly a rebase after zipfian function commit.

This patch adds \if support to pgbench, similar to psql's version added in
March.

This patch brings a consistent set of features especially when combined with
two other patches already in the (slow) CF process:

- https://commitfest.postgresql.org/10/596/ .. /15/985/
adds support for booleans expressions (comparisons, logical
operators, ...). This enhanced expression engine would be useful
to allow client-side expression in psql.

- https://commitfest.postgresql.org/10/669/ .. /15/669/
adds support for \gset, so that pgbench can interact with a database
and extract something into a variable, instead of discarding it.

This patch adds a \if construct so that an expression on variables, possibly
with data coming from the database, can change the behavior of a script.

For instance, the following script, which uses features from the three
patches, would implement TPC-B per spec (not "tpcb-like", but really as
specified).

\set tbid random(1, :scale)
\set tid 10 * (:tbid - 1) + random(1, 10)
-- client in same branch as teller at 85%
\if :scale = 1 OR random(0, 99) < 85
-- same branch
\set bid :tbid
\else
-- different branch
\set bid 1 + (:tbid + random(1, :scale - 1)) % :scale
\endif
\set aid :bid * 100000 + random(1, 100000)
\set delta random(-999999, 999999)
BEGIN;
UPDATE pgbench_accounts
SET abalance = abalance + :delta WHERE aid = :aid
RETURNING abalance AS balance \gset
UPDATE pgbench_tellers
SET tbalance = tbalance + :delta WHERE tid = :tid;
UPDATE pgbench_branches
SET bbalance = bbalance + :delta WHERE bid = :bid;
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime)
VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);
END;

The patch moves the conditional stack infrastructure from psql to fe_utils,
so that it is available to both psql & pgbench.

A partial evaluation is performed to detect structural errors (eg missing
endif, else after else...) when the script is parsed, so that such errors
cannot occur when a script is running.

A new automaton state is added to quickly step over false branches.

TAP tests ensure reasonable coverage of the feature.

--
Fabien.

Attachments:

pgbench-if-2.patchtext/x-diff; name=pgbench-if-2.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 4431fc3..bbc10b5 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 7ce6f60..7aa45f6 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"
@@ -273,6 +274,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.
 	 *
@@ -282,6 +286,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,
@@ -311,6 +316,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 */
@@ -399,7 +405,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
@@ -1468,6 +1478,27 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		return true;
 	}
 }
+
+/*
+ * Return true or false for conditional purposes.
+ * Non zero numerical values are true.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_INT:
+			return pval->u.ival != 0;
+		case PGBT_DOUBLE:
+			return pval->u.dval != 0.0;
+		default:
+			/* internal error, unexpected type */
+			Assert(false);
+			return false;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1925,6 +1956,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;
@@ -2046,11 +2085,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. */
@@ -2238,6 +2277,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;
 
 				/*
@@ -2403,7 +2444,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
@@ -2436,7 +2477,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;
 						}
@@ -2447,77 +2488,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 (!putVariableNumber(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;
 
@@ -2530,7 +2703,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;
 				}
@@ -2552,7 +2725,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;
@@ -2596,9 +2769,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;
 
 				/*
@@ -2609,6 +2783,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)
 				{
 					PQfinish(st->con);
@@ -3429,19 +3610,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]);
 
@@ -3537,6 +3724,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 */
@@ -3549,6 +3742,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.
@@ -3834,6 +4083,8 @@ addScript(ParsedScript script)
 		exit(1);
 	}
 
+	CheckConditional(script);
+
 	sql_script[num_scripts] = script;
 	num_scripts++;
 }
@@ -4580,6 +4831,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 e3cdf28..27f4413 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -232,7 +232,14 @@ pgbench(
 		qr{command=19.: double 19\b},
 		qr{command=20.: double 20\b},
 		qr{command=21.: int 9223372036854775807\b},
-		qr{command=23.: int [1-9]\b}, ],
+		qr{command=23.: int [1-9]\b},
+		qr{command=26.: int 26\b},
+		qr{command=35.: int 35\b},
+		qr{command=44.: int 44\b},
+		qr{command=47.: int 47\b},
+		qr{command=54.: int 54\b},
+		qr{command=56.: int 0\b},
+		],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
 \set i1 debug(random(1, 100))
@@ -264,6 +271,41 @@ pgbench(
 \set i1 0
 -- yet another integer function
 \set id debug(random_zipfian(1, 9, 1.3))
+-- if tests
+\set nope 0
+\if 1
+\set id debug(26)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ie debug(35)
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 0
+\set nope 1
+\else
+\set if debug(44)
+\endif
+\if 1
+\set ig debug(47)
+\elif 0
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ih debug(54)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 # backslash commands
@@ -311,7 +353,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 cabfe15..1490c33 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 7aedd0d..1d19849 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 63977ce..0000000
--- a/src/bin/psql/conditional.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2017, 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 0957627..0000000
--- a/src/bin/psql/conditional.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2017, 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 a7a95ef..0972e50 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 e3cde04..074b530 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 ebce38c..4d31819 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..19e04fe
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..f7eb0b6
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, 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 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 */
#3Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#2)
1 attachment(s)
Re: pgbench - add \if support

Another rebase after the pow function commit.

Mostly a rebase after zipfian function commit.

--
Fabien.

Attachments:

pgbench-if-3.patchtext/x-diff; name=pgbench-if-3.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1519fe7..7068063 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 e065f7b..ea022ff 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"
@@ -273,6 +274,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.
 	 *
@@ -282,6 +286,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,
@@ -311,6 +316,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 */
@@ -399,7 +405,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
@@ -1468,6 +1478,27 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		return true;
 	}
 }
+
+/*
+ * Return true or false for conditional purposes.
+ * Non zero numerical values are true.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_INT:
+			return pval->u.ival != 0;
+		case PGBT_DOUBLE:
+			return pval->u.dval != 0.0;
+		default:
+			/* internal error, unexpected type */
+			Assert(false);
+			return false;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1943,6 +1974,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;
@@ -2064,11 +2103,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. */
@@ -2256,6 +2295,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;
 
 				/*
@@ -2421,7 +2462,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
@@ -2454,7 +2495,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;
 						}
@@ -2465,77 +2506,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 (!putVariableNumber(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;
 
@@ -2548,7 +2721,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;
 				}
@@ -2570,7 +2743,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;
@@ -2614,9 +2787,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;
 
 				/*
@@ -2627,6 +2801,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)
 				{
 					PQfinish(st->con);
@@ -3447,19 +3628,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]);
 
@@ -3555,6 +3742,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 */
@@ -3567,6 +3760,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.
@@ -3852,6 +4101,8 @@ addScript(ParsedScript script)
 		exit(1);
 	}
 
+	CheckConditional(script);
+
 	sql_script[num_scripts] = script;
 	num_scripts++;
 }
@@ -4598,6 +4849,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 3dd080e..ec8aaf2 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -242,6 +242,12 @@ pgbench(
 		qr{command=30.: double -0.00032\b},
 		qr{command=31.: double 8.50705917302346e\+0?37\b},
 		qr{command=32.: double 1e\+0?30\b},
+		qr{command=35.: int 35\b},
+		qr{command=44.: int 44\b},
+		qr{command=53.: int 53\b},
+		qr{command=56.: int 56\b},
+		qr{command=63.: int 63\b},
+		qr{command=65.: int 0\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
@@ -284,6 +290,41 @@ pgbench(
 \set powernegd2 debug(power(-5.0,-5.0))
 \set powerov debug(pow(9223372036854775807, 2))
 \set powerov2 debug(pow(10,30))
+-- if tests
+\set nope 0
+\if 1
+\set id debug(35)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ie debug(44)
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 0
+\set nope 1
+\else
+\set if debug(53)
+\endif
+\if 1
+\set ig debug(56)
+\elif 0
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ih debug(63)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 # backslash commands
@@ -331,7 +372,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 cabfe15..1490c33 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 7aedd0d..1d19849 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 63977ce..0000000
--- a/src/bin/psql/conditional.c
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2017, 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 0957627..0000000
--- a/src/bin/psql/conditional.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * psql - the PostgreSQL interactive terminal
- *
- * Copyright (c) 2000-2017, 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 a7a95ef..0972e50 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 e3cde04..074b530 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 ebce38c..4d31819 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..19e04fe
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..f7eb0b6
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2017, 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 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 */
#4Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#3)
1 attachment(s)
Re: pgbench - add \if support

Another rebase to try to please the patch tester.

--
Fabien.

Attachments:

pgbench-if-4.patchtext/x-diff; name=pgbench-if-4.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1519fe7..7068063 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 fc2c734..a4811b3 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"
@@ -273,6 +274,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.
 	 *
@@ -282,6 +286,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,
@@ -311,6 +316,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 */
@@ -399,7 +405,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
@@ -1468,6 +1478,27 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		return true;
 	}
 }
+
+/*
+ * Return true or false for conditional purposes.
+ * Non zero numerical values are true.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_INT:
+			return pval->u.ival != 0;
+		case PGBT_DOUBLE:
+			return pval->u.dval != 0.0;
+		default:
+			/* internal error, unexpected type */
+			Assert(false);
+			return false;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1943,6 +1974,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;
@@ -2064,11 +2103,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. */
@@ -2256,6 +2295,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;
 
 				/*
@@ -2421,7 +2462,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
@@ -2454,7 +2495,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;
 						}
@@ -2465,77 +2506,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 (!putVariableNumber(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;
 
@@ -2548,7 +2721,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;
 				}
@@ -2570,7 +2743,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;
@@ -2614,9 +2787,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;
 
 				/*
@@ -2627,6 +2801,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)
 				{
 					PQfinish(st->con);
@@ -3447,19 +3628,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]);
 
@@ -3555,6 +3742,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 */
@@ -3567,6 +3760,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.
@@ -3852,6 +4101,8 @@ addScript(ParsedScript script)
 		exit(1);
 	}
 
+	CheckConditional(script);
+
 	sql_script[num_scripts] = script;
 	num_scripts++;
 }
@@ -4598,6 +4849,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 3dd080e..ec8aaf2 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -242,6 +242,12 @@ pgbench(
 		qr{command=30.: double -0.00032\b},
 		qr{command=31.: double 8.50705917302346e\+0?37\b},
 		qr{command=32.: double 1e\+0?30\b},
+		qr{command=35.: int 35\b},
+		qr{command=44.: int 44\b},
+		qr{command=53.: int 53\b},
+		qr{command=56.: int 56\b},
+		qr{command=63.: int 63\b},
+		qr{command=65.: int 0\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
@@ -284,6 +290,41 @@ pgbench(
 \set powernegd2 debug(power(-5.0,-5.0))
 \set powerov debug(pow(9223372036854775807, 2))
 \set powerov2 debug(pow(10,30))
+-- if tests
+\set nope 0
+\if 1
+\set id debug(35)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ie debug(44)
+\else
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 0
+\set nope 1
+\else
+\set if debug(53)
+\endif
+\if 1
+\set ig debug(56)
+\elif 0
+\set nope 1
+\endif
+\if 0
+\set nope 1
+\elif 1
+\set ih debug(63)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 # backslash commands
@@ -331,7 +372,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..ac6487d
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..55206b8
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * 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 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 */
#5Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Fabien COELHO (#4)
Re: pgbench - add \if support

On 01/04/2018 07:32 AM, Fabien COELHO wrote:

Another rebase to try to please the patch tester.

Thank you. I plan on reviewing this over the weekend.
--
Vik Fearing +33 6 46 75 15 36
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#6Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Vik Fearing (#5)
Re: pgbench - add \if support

On 4 January 2018 at 07:32, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Another rebase to try to please the patch tester.

Thanks for working on this. I had just a quick look at it, but I hope I'll have
time to post a proper review. In the meantime I'm wondering what am I doing
wrong here (I see a similar example in your first message)?

```
-- test.sql
\if random(0, 99) < 85
\set test 1
\else
\set test 2
\endif
select :test;
```

```
$ pgbench -s 10 -f test.sql
test.sql:1: unexpected character (<) in command "if"
\if random(0, 99) < 85
^ error found here
```

I'm using `pgbench-if-4.patch`, and everything is fine for simple
conditions like `\if 1`.

#7Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Dmitry Dolgov (#6)
Re: pgbench - add \if support

Hello Dmitry,

Thanks for working on this. I had just a quick look at it, but I hope
I'll have time to post a proper review. In the meantime I'm wondering
what am I doing wrong here (I see a similar example in your first
message)?

```
-- test.sql
\if random(0, 99) < 85
\set test 1
\else
\set test 2
\endif
select :test;
```

```
$ pgbench -s 10 -f test.sql
test.sql:1: unexpected character (<) in command "if"
\if random(0, 99) < 85
^ error found here

Sure.

What you are trying to do is the result of combining the pgbench-if patch
and the pgbench-more-ops-and-funcs patch.

There is also with the ability to put the result of a SELECT into a
variable, which would then enable doing some if about data coming
from the database.

https://commitfest.postgresql.org/16/985/
https://commitfest.postgresql.org/16/669/
https://commitfest.postgresql.org/16/1385/

These are distinct entries in the CF, because they do quite distinct
things, and interact weakly one with the other.

However, it really makes full sense when they are all available together,
so I put an example which combines all three. "\if 1" is not that
interesting in itself, obviously.

Everytime I sent a (relatively) big patch in the past I was asked to cut
it in bites, which is tiresome when everything is intermixed, so now I'm
doing the bytes first.

--
Fabien.

#8Dmitry Dolgov
9erthalion6@gmail.com
In reply to: Fabien COELHO (#7)
Re: pgbench - add \if support

On 8 January 2018 at 19:36, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

What you are trying to do is the result of combining the pgbench-if patch
and the pgbench-more-ops-and-funcs patch.

Oh, I see. I missed the first message about this patch, sorry. But for some
reason I can't apply both patches (pgbench-more-ops-funcs-23.patch and
pgbench-if-4.patch) in any order:

Hunk #24 FAILED at 2824.
Hunk #25 succeeded at 5178 (offset 251 lines).
1 out of 25 hunks FAILED -- saving rejects to file
src/bin/pgbench/pgbench.c.rej
(Stripping trailing CRs from patch; use --binary to disable.)
patching file src/bin/pgbench/pgbench.h
(Stripping trailing CRs from patch; use --binary to disable.)
patching file src/bin/pgbench/t/001_pgbench_with_server.pl
Hunk #2 FAILED at 226.
Hunk #3 FAILED at 287.
Hunk #4 succeeded at 448 (offset 41 lines).
Hunk #5 succeeded at 505 (offset 41 lines).
Hunk #6 succeeded at 516 (offset 41 lines).
2 out of 6 hunks FAILED -- saving rejects to file
src/bin/pgbench/t/001_pgbench_with_server.pl.rej

Is there any other dependency I should apply?

#9Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Dmitry Dolgov (#8)
Re: pgbench - add \if support

Hello Dmitry,

What you are trying to do is the result of combining the pgbench-if patch
and the pgbench-more-ops-and-funcs patch.

Oh, I see. I missed the first message about this patch, sorry. But for some
reason I can't apply both patches (pgbench-more-ops-funcs-23.patch and
pgbench-if-4.patch) in any order:

Hunk #24 FAILED at 2824. [...]

Indeed, they interfere.

Is there any other dependency I should apply?

No, the patches really conflict in minor ways. They are expected to be
reviewed independently. If/when one feature is committed, I('ll) update
the remaining patches so that they still work.

If I produce dependent patches ISTM that it makes a more complicated
review, so the patches are less likely to be reviewed and maybe finally
committed.

I just wanted to point out that the the features are more interestings
when combined than on their own.

--
Fabien.

#10Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Fabien COELHO (#1)
Re: pgbench - add \if support

On 11/25/2017 10:33 PM, Fabien COELHO wrote:

This patch adds \if support to pgbench, similar to psql's version added
in March.

This patch brings a consistent set of features especially when combined
with two other patches already in the (slow) CF process:

�- https://commitfest.postgresql.org/10/596/ .. /15/985/
�� adds support for booleans expressions (comparisons, logical
�� operators, ...). This enhanced expression engine would be useful
�� to allow client-side expression in psql.

�- https://commitfest.postgresql.org/10/669/ .. /15/669/
�� adds support for \gset, so that pgbench can interact with a database
�� and extract something into a variable, instead of discarding it.

This patch adds a \if construct so that an expression on variables,
possibly with data coming from the database, can change the behavior of
a script.

I have given this patch a pretty good shake and I'm happy with it. I
did not test it with the other two patches, only on its own.

A partial evaluation is performed to detect structural errors (eg
missing endif, else after else...) when the script is parsed, so that
such errors cannot occur when a script is running.

Very good.

A new automaton state is added to quickly step over false branches.

This one took me a little while to understand while reading the patch,
but mostly because of how diff doesn't handle moving things around.

TAP tests ensure reasonable coverage of the feature.

And the documentation seems sufficient, as well.

It's a shame this feature uses \elif instead of \elsif to be closer to
plpgsql, but I suppose this ship already sailed when psql chose \elif,
and I think it is correct that this patch follows psql.

Marking as ready for committer.
--
Vik Fearing +33 6 46 75 15 36
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#11Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Vik Fearing (#10)
Re: pgbench - add \if support

Hello Vik,

This patch adds a \if construct so that an expression on variables,
possibly with data coming from the database, can change the behavior of
a script.

I have given this patch a pretty good shake and I'm happy with it. I
did not test it with the other two patches, only on its own.

As noted by Dmitry, they intefere slightly one with the other so it
would require some conflict resolution.

[...]

Marking as ready for committer.

Ok, thanks.

--
Fabien.

#12Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Vik Fearing (#10)
Re: pgbench - add \if support

A new automaton state is added to quickly step over false branches.

This one took me a little while to understand while reading the patch,
but mostly because of how diff doesn't handle moving things around.

"git diff -w --patience" may help.

Marking as ready for committer.

Here is a rebase. I made some tests use actual expressions instead of just
0 and 1. No other changes.

--
Fabien.

#13Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#12)
1 attachment(s)
Re: pgbench - add \if support

Here is a rebase. I made some tests use actual expressions instead of just 0
and 1. No other changes.

Sigh. Better with the attachment. Sorry for the noise.

--
Fabien.

Attachments:

pgbench-if-5.patchtext/x-diff; charset=us-ascii; name=pgbench-if-5.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 3dd492c..c203c41 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 31ea6ca..b5ebefc 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"
@@ -274,6 +275,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.
 	 *
@@ -283,6 +287,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,
@@ -312,6 +317,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 */
@@ -400,7 +406,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
@@ -1587,6 +1597,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)
@@ -2295,6 +2306,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;
@@ -2416,11 +2435,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. */
@@ -2608,6 +2627,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;
 
 				/*
@@ -2773,7 +2794,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
@@ -2806,7 +2827,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;
 						}
@@ -2817,77 +2838,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;
 
@@ -2900,7 +3053,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;
 				}
@@ -2922,7 +3075,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;
@@ -2966,9 +3119,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;
 
 				/*
@@ -2979,6 +3133,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)
 				{
 					PQfinish(st->con);
@@ -3799,19 +3960,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]);
 
@@ -3907,6 +4074,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 */
@@ -3919,6 +4092,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.
@@ -4204,6 +4433,8 @@ addScript(ParsedScript script)
 		exit(1);
 	}
 
+	CheckConditional(script);
+
 	sql_script[num_scripts] = script;
 	num_scripts++;
 }
@@ -4950,6 +5181,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 e579334..1d3d134 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -239,7 +239,7 @@ pgbench(
 		qr{command=26.: double -0.125\b},
 		qr{command=27.: double -0.00032\b},
 		qr{command=28.: double 8.50705917302346e\+0?37\b},
-		qr{command=29.: double 1e\+30\b},
+		qr{command=29.: double 1e\+0?30\b},
 		qr{command=30.: boolean false\b},
 		qr{command=31.: boolean true\b},
 		qr{command=32.: int 32\b},
@@ -259,6 +259,12 @@ pgbench(
 		qr{command=46.: int 46\b},
 		qr{command=47.: boolean true\b},
 		qr{command=48.: boolean true\b},
+		qr{command=60.: int 60\b},
+		qr{command=69.: int 69\b},
+		qr{command=78.: int 78\b},
+		qr{command=81.: int 81\b},
+		qr{command=88.: int 88\b},
+		qr{command=90.: int 0\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
@@ -338,6 +344,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(60)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 > 0
+\set ie debug(69)
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 < 0
+\set nope 1
+\else
+\set if debug(78)
+\endif
+\if 1 = 1
+\set ig debug(81)
+\elif 0
+\set nope 1
+\endif
+\if 1 = 0
+\set nope 1
+\elif 1 <> 0
+\set ih debug(88)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 =head
@@ -391,7 +432,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..ac6487d
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..55206b8
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * 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 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 */
#14Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#13)
Re: pgbench - add \if support

Hi!

Hm, isn't already commited when/case/then/else syntax do the same? If not, could
it be added to existing synax?

Fabien COELHO wrote:

Here is a rebase. I made some tests use actual expressions instead of just 0
and 1. No other changes.

Sigh. Better with the attachment. Sorry for the noise.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#15Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#14)
Re: pgbench - add \if support

Hm, isn't already commited when/case/then/else syntax do the same?

No, not strictly. The "CASE WHEN" is an if *within* an expression:

\set i CASE WHEN condition THEN val1 ELSE val2 END

The \if is at the script level, like psql already available version, which
can change what SQL is sent.

\if condition
SOME SQL
\else
OTHER SQL
\endif

You could achieve the CASE semantics with some \if:

\if condition
\set i val1
\else
\set i val2
\endif

But the reverse is not possible.

--
Fabien.

#16Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#13)
1 attachment(s)
Re: pgbench - add \if support

Here is a rebase. I made some tests use actual expressions instead of just
0 and 1. No other changes.

Sigh. Better with the attachment. Sorry for the noise.

Here is a very minor rebase.

--
Fabien.

Attachments:

pgbench-if-6.patchtext/x-diff; name=pgbench-if-6.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 3dd492c..c203c41 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 31ea6ca..b5ebefc 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"
@@ -274,6 +275,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.
 	 *
@@ -283,6 +287,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,
@@ -312,6 +317,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 */
@@ -400,7 +406,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
@@ -1587,6 +1597,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)
@@ -2295,6 +2306,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;
@@ -2416,11 +2435,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. */
@@ -2608,6 +2627,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;
 
 				/*
@@ -2773,7 +2794,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
@@ -2806,7 +2827,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;
 						}
@@ -2817,77 +2838,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;
 
@@ -2900,7 +3053,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;
 				}
@@ -2922,7 +3075,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;
@@ -2966,9 +3119,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;
 
 				/*
@@ -2979,6 +3133,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)
 				{
 					PQfinish(st->con);
@@ -3799,19 +3960,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]);
 
@@ -3907,6 +4074,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 */
@@ -3919,6 +4092,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.
@@ -4204,6 +4433,8 @@ addScript(ParsedScript script)
 		exit(1);
 	}
 
+	CheckConditional(script);
+
 	sql_script[num_scripts] = script;
 	num_scripts++;
 }
@@ -4950,6 +5181,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 a8b2962..1d3d134 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -259,6 +259,12 @@ pgbench(
 		qr{command=46.: int 46\b},
 		qr{command=47.: boolean true\b},
 		qr{command=48.: boolean true\b},
+		qr{command=60.: int 60\b},
+		qr{command=69.: int 69\b},
+		qr{command=78.: int 78\b},
+		qr{command=81.: int 81\b},
+		qr{command=88.: int 88\b},
+		qr{command=90.: int 0\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
@@ -338,6 +344,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(60)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 > 0
+\set ie debug(69)
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 < 0
+\set nope 1
+\else
+\set if debug(78)
+\endif
+\if 1 = 1
+\set ig debug(81)
+\elif 0
+\set nope 1
+\endif
+\if 1 = 0
+\set nope 1
+\elif 1 <> 0
+\set ih debug(88)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 =head
@@ -391,7 +432,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..ac6487d
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..55206b8
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * 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 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 */
#17Craig Ringer
craig@2ndquadrant.com
In reply to: Fabien COELHO (#16)
Re: pgbench - add \if support

On 16 January 2018 at 06:28, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a rebase. I made some tests use actual expressions instead of just

0 and 1. No other changes.

Sigh. Better with the attachment. Sorry for the noise.

Here is a very minor rebase.

I'm a smidge worried about this. It seems like psql is growing a scripting
language. Do we want to go our own way with a kind of organically grown
scripting system? Or should we be looking at embedding client-side
scripting in a more structured, formal way instead? Embed a lua interpreter
or something?

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#18Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Craig Ringer (#17)
Re: pgbench - add \if support

Helo Craig,

I'm a smidge worried about this. It seems like psql is growing a
scripting language.

The patch is about aligning pgbench with psql, which already has \if.

Do we want to go our own way with a kind of organically grown
scripting system? Or should we be looking at embedding client-side
scripting in a more structured, formal way instead? Embed a lua interpreter
or something?

My 0.02€ is that the point is to deal with useful/needed simple client
capabilities while integrating gracefully with bare server-side executed
SQL.

As for useful client-side capabilities, for both psql & pgbench ISTM that
it is more in line with a limited cpp-like thing: include, expressions,
variables, conditions... maybe minimal error handling. No loop.

As for a language interpreter, it would raise the question of which
language (lua, tcl, python, perl, VB, sh, R, ...) and the graceful (upward
compatible) integration of any such language: eg how do have pieces of
bare SQL and any other existing language would require some scanning
conventions that do not exist.

psql & pgbench already have ":x" variables. psql has the ability to set
variable from SQL (\gset), and pgbench could do limited expressions to set
these variables with (\set), which have been extended to be more complete
, and there was use cases which motivate an (\if).

ISTM enough to align both tools for reasonnably simple use cases that
could arise when running a basic SQL script of bench. If you have
something really complicated, then full fledge programming is the answer,
which cannot be bare-SQL compatible.

So the answer is that it is okay to aim at "limited" scripting because it
covers useful use cases.

--
Fabien.

#19Pavel Stehule
pavel.stehule@gmail.com
In reply to: Craig Ringer (#17)
Re: pgbench - add \if support

2018-01-21 23:31 GMT+01:00 Craig Ringer <craig@2ndquadrant.com>:

On 16 January 2018 at 06:28, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a rebase. I made some tests use actual expressions instead of

just 0 and 1. No other changes.

Sigh. Better with the attachment. Sorry for the noise.

Here is a very minor rebase.

I'm a smidge worried about this. It seems like psql is growing a scripting
language. Do we want to go our own way with a kind of organically grown
scripting system? Or should we be looking at embedding client-side
scripting in a more structured, formal way instead? Embed a lua interpreter
or something?

few scripting features doesn't mean scripting language. \if in psql is nice
feature that reduce duplicate code, unreadable code, and helps with
deployment and test scripts. pgbench and psql should to have similar
environment - and I am thinking so \if should be there.

Using Lua is not bad idea - in psql too - I though about it much, but in
this case is better to start from zero.

Regards

Pavel

Show quoted text

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

#20Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Pavel Stehule (#19)
Re: pgbench - add \if support

few scripting features doesn't mean scripting language. \if in psql is nice
feature that reduce duplicate code, unreadable code, and helps with
deployment and test scripts. pgbench and psql should to have similar
environment - and I am thinking so \if should be there.

Using Lua is not bad idea - in psql too - I though about it much, but in
this case is better to start from zero.

Yep. Having another versatile (interactive) client would not be a bad
thing. I'm still wondering how to conciliate any scripting language with
"bare SQL". The backslash-oriented syntax already used for psql & pgbench
seems the only available option. Otherwise ISTM that it is back to a
standard library oriented client access with import, connect, exec...
whatever set of function already provided by standard libraries (psycopg
for python, ...).

--
Fabien.

#21Pavel Stehule
pavel.stehule@gmail.com
In reply to: Fabien COELHO (#20)
Re: pgbench - add \if support

2018-01-22 10:45 GMT+01:00 Fabien COELHO <coelho@cri.ensmp.fr>:

few scripting features doesn't mean scripting language. \if in psql is nice

feature that reduce duplicate code, unreadable code, and helps with
deployment and test scripts. pgbench and psql should to have similar
environment - and I am thinking so \if should be there.

Using Lua is not bad idea - in psql too - I though about it much, but in
this case is better to start from zero.

Yep. Having another versatile (interactive) client would not be a bad
thing. I'm still wondering how to conciliate any scripting language with
"bare SQL". The backslash-oriented syntax already used for psql & pgbench
seems the only available option. Otherwise ISTM that it is back to a
standard library oriented client access with import, connect, exec...
whatever set of function already provided by standard libraries (psycopg
for python, ...).

The implementation of some parts in C is frustrating - mainly tab complete.
There is not possibility to create own backslash command - or enhance
buildin commands. Is not possible to customize output.

So some hypothetical client can be implemented like some core C module -
for fast processing of tabular data and all other can be implemented in
Lua. I can imagine so this client can support some input forms, for bar
menu, for some simple reports. It can be more like FoxPro client than
command line only client. In few years we can use ncurses everywhere, and
then there are possibility to write rich TUI client.

Regards

Pavel

Show quoted text

--
Fabien.

#22Andres Freund
andres@anarazel.de
In reply to: Fabien COELHO (#16)
Re: pgbench - add \if support

Hi,

On 2018-01-15 18:28:09 +0100, Fabien COELHO wrote:

Here is a very minor rebase.

Your recent patches all seem to have windows line-endings. It'd be good
to fix that for the future...

- Andres

#23Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Andres Freund (#22)
Re: pgbench - add \if support

Hello Andres,

Here is a very minor rebase.

Your recent patches all seem to have windows line-endings.

There does not seem to be any on my side:

sh> hexdump pgbench-if-6.patch | grep '0[ad]' | head -3
0000040 6562 636e 2e68 6773 6c6d 690a 646e 7865
0000060 2031 3031 3630 3434 2d0a 2d2d 6120 642f
0000080 6770 6562 636e 2e68 6773 6c6d 2b0a 2b2b

There are a few LF (0a) but no CR (0d) visible.

I'd guess that you are running on windows, that the mime-type of the
attachement is "text/x-diff", which is what my ubuntu box lists as
appropriate for "*.patch", and that because of "text/" your mail client
would have decided to switch "\n" to "\r\n" on its own?

It'd be good to fix that for the future...

What should I do about transformations on the receiving side?

--
Fabien.

#24Daniel Verite
daniel@manitou-mail.org
In reply to: Fabien COELHO (#23)
Re: pgbench - add \if support

Fabien COELHO wrote:

There does not seem to be any on my side:

sh> hexdump pgbench-if-6.patch | grep '0[ad]' | head -3
0000040 6562 636e 2e68 6773 6c6d 690a 646e 7865
0000060 2031 3031 3630 3434 2d0a 2d2d 6120 642f
0000080 6770 6562 636e 2e68 6773 6c6d 2b0a 2b2b

FYI when looking at the corresponding raw mailfile as we
subscribers receive it, here's what I got:

(for Message-ID: <alpine.DEB.2.20.1801151827260.11126@lancre>)

==== start of raw mailfile excerpt ====
Here is a very minor rebase.

--
Fabien.
--8323329-978247287-1516037290=:11126
Content-Type: text/x-diff; name=pgbench-if-6.patch
Content-Transfer-Encoding: BASE64
Content-ID: <alpine.DEB.2.20.1801151828090.11126@lancre>
Content-Description:
Content-Disposition: attachment; filename=pgbench-if-6.patch

ZGlmZiAtLWdpdCBhL2RvYy9zcmMvc2dtbC9yZWYvcGdiZW5jaC5zZ21sIGIv
ZG9jL3NyYy9zZ21sL3JlZi9wZ2JlbmNoLnNnbWwNCmluZGV4IDNkZDQ5MmMu
LmMyMDNjNDEgMTAwNjQ0DQotLS0gYS9kb2Mvc3JjL3NnbWwvcmVmL3BnYmVu
Y2guc2dtbA0KKysrIGIvZG9jL3NyYy9zZ21sL3JlZi9wZ2JlbmNoLnNnbWwN
CkBAIC04OTUsNiArODk1LDIxIEBAIHBnYmVuY2ggPG9wdGlvbmFsPiA8cmVw
bGFjZWFibGU+b3B0aW9uczwvcmVwbGFjZWFibGU+IDwvb3B0aW9uYWw+IDxy
==== cut here ====

Now this base64 part piped through `base64 -d` and then `hexdump -C`
gives:
00000000 64 69 66 66 20 2d 2d 67 69 74 20 61 2f 64 6f 63 |diff --git
a/doc|
00000010 2f 73 72 63 2f 73 67 6d 6c 2f 72 65 66 2f 70 67
|/src/sgml/ref/pg|
00000020 62 65 6e 63 68 2e 73 67 6d 6c 20 62 2f 64 6f 63 |bench.sgml
b/doc|
00000030 2f 73 72 63 2f 73 67 6d 6c 2f 72 65 66 2f 70 67
|/src/sgml/ref/pg|
00000040 62 65 6e 63 68 2e 73 67 6d 6c 0d 0a 69 6e 64 65
|bench.sgml..inde|
00000050 78 20 33 64 64 34 39 32 63 2e 2e 63 32 30 33 63 |x
3dd492c..c203c|
00000060 34 31 20 31 30 30 36 34 34 0d 0a 2d 2d 2d 20 61 |41 100644..---
a|
00000070 2f 64 6f 63 2f 73 72 63 2f 73 67 6d 6c 2f 72 65
|/doc/src/sgml/re|
00000080 66 2f 70 67 62 65 6e 63 68 2e 73 67 6d 6c 0d 0a
|f/pgbench.sgml..|
00000090 2b 2b 2b 20 62 2f 64 6f 63 2f 73 72 63 2f 73 67 |+++
b/doc/src/sg|
000000a0 6d 6c 2f 72 65 66 2f 70 67 62 65 6e 63 68 2e 73
|ml/ref/pgbench.s|
000000b0 67 6d 6c 0d 0a 40 40 20 2d 38 39 35 2c 36 20 2b |gml..@@ -895,6
+|

Line endings are already 0d 0a at this point, which normally means
that they were like that in the mail as you submitted it.

Best regards,
--
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite

#25Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Daniel Verite (#24)
1 attachment(s)
Re: pgbench - add \if support

00000040 62 65 6e 63 68 2e 73 67 6d 6c 0d 0a 69 6e 64 65

Hmmm. "0d 0a" looks bad.

I've meddled into "/etc/mime.types" to change "text/x-diff" to
"text/plain" used by some other mailers. Here is the v6 version again,
size is 38424 and md5sum is 63d79c0d5a93294f002edc640a3f525b.

--
Fabien.

Attachments:

pgbench-if-6.patchtext/plain; name=pgbench-if-6.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 3dd492c..c203c41 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -895,6 +895,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 fce7e3a..e88f782 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2148,7 +2148,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 31ea6ca..b5ebefc 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"
@@ -274,6 +275,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.
 	 *
@@ -283,6 +287,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,
@@ -312,6 +317,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 */
@@ -400,7 +406,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
@@ -1587,6 +1597,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)
@@ -2295,6 +2306,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;
@@ -2416,11 +2435,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. */
@@ -2608,6 +2627,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;
 
 				/*
@@ -2773,7 +2794,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
@@ -2806,7 +2827,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;
 						}
@@ -2817,77 +2838,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;
 
@@ -2900,7 +3053,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;
 				}
@@ -2922,7 +3075,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;
@@ -2966,9 +3119,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;
 
 				/*
@@ -2979,6 +3133,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)
 				{
 					PQfinish(st->con);
@@ -3799,19 +3960,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]);
 
@@ -3907,6 +4074,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 */
@@ -3919,6 +4092,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.
@@ -4204,6 +4433,8 @@ addScript(ParsedScript script)
 		exit(1);
 	}
 
+	CheckConditional(script);
+
 	sql_script[num_scripts] = script;
 	num_scripts++;
 }
@@ -4950,6 +5181,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 a8b2962..1d3d134 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -259,6 +259,12 @@ pgbench(
 		qr{command=46.: int 46\b},
 		qr{command=47.: boolean true\b},
 		qr{command=48.: boolean true\b},
+		qr{command=60.: int 60\b},
+		qr{command=69.: int 69\b},
+		qr{command=78.: int 78\b},
+		qr{command=81.: int 81\b},
+		qr{command=88.: int 88\b},
+		qr{command=90.: int 0\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
@@ -338,6 +344,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(60)
+\elif 0
+\set nope 1
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 > 0
+\set ie debug(69)
+\else
+\set nope 1
+\endif
+\if 1 < 0
+\set nope 1
+\elif 1 < 0
+\set nope 1
+\else
+\set if debug(78)
+\endif
+\if 1 = 1
+\set ig debug(81)
+\elif 0
+\set nope 1
+\endif
+\if 1 = 0
+\set nope 1
+\elif 1 <> 0
+\set ih debug(88)
+\endif
+-- must be zero if false branches where skipped
+\set nope debug(:nope)
 } });
 
 =head
@@ -391,7 +432,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..ac6487d
--- /dev/null
+++ b/src/fe_utils/conditional.c
@@ -0,0 +1,174 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2018, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/conditional.c
+ */
+#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..55206b8
--- /dev/null
+++ b/src/include/fe_utils/conditional.h
@@ -0,0 +1,85 @@
+/*
+ * 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 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 */
#26Andres Freund
andres@anarazel.de
In reply to: Fabien COELHO (#25)
Re: pgbench - add \if support

Hi Fabien, Daniel,

On 2018-03-01 18:47:04 +0100, Fabien COELHO wrote:

00000040 62 65 6e 63 68 2e 73 67 6d 6c 0d 0a 69 6e 64 65

Thanks for the analysis.

Hmmm. "0d 0a" looks bad.

It does. TBQH the aesthetics of seing ^M's all over is what made me
complain ;)

I've meddled into "/etc/mime.types" to change "text/x-diff" to "text/plain"
used by some other mailers. Here is the v6 version again, size is 38424 and
md5sum is 63d79c0d5a93294f002edc640a3f525b.

This does look better. Not quite sure what the problem is, but ...

Greetings,

Andres Freund

#27Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#15)
Re: pgbench - add \if support

No, not strictly. The "CASE WHEN" is an if *within* an expression:Okay, I see.

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.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/

#28Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#27)
1 attachment(s)
Re: pgbench - add \if support

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.

Attachments:

pgbench-if-7.patchtext/plain; name=pgbench-if-7.patchDownload
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 */
#29Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#28)
Re: pgbench - add \if support

Thank you, pushed

Fabien COELHO wrote:

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.

--
Teodor Sigaev E-mail: teodor@sigaev.ru
WWW: http://www.sigaev.ru/