Another rebase after the pow function commit.

Mostly a rebase after zipfian function commit.

--
Fabien.
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 */

Reply via email to