pgbench more operators & functions

Started by Fabien COELHOalmost 10 years ago94 messages
#1Fabien COELHO
coelho@cri.ensmp.fr
2 attachment(s)

Hello,

Here is a simple patch which adds a bunch of operators (bitwise: & | ^ ~,
comparisons: =/== <>/!= < <= > >=, logical: and/&& or/|| xor/^^ not/!) and
functions (exp ln if) to pgbench. I've tried to be pg's SQL compatible
where appropriate.

Also attached is a simple test script.

Some kind of continuations in \ commands would be a good thing.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-1.patchtext/x-diff; name=pgbench-more-ops-funcs-1.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 06cd5db..def6761 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -821,9 +821,17 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
+      arithmetic operators (unary: <literal>+</>, <literal>-</>; binary:
+      <literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>, <literal>%</>),
+      bitwise operators (unary: <literal>~</>; binary: <literal>&amp;</>,
+      <literal>|</>, <literal>#</>/<literal>^</>, <literal>&lt;&lt;</>,
+      <literal>&gt;&gt;</>)
+      comparisons (<literal>=</>/<literal>==</>, <literal>&lt;&gt;</>/<literal>!=</>,
+      <literal>&lt;=</>, <literal>&lt;</>, <literal>&gt;=</>, <literal>&gt;</>),
+      logical operators (unary: <literal>not</>/<literal>!</>;
+      binary: <literal>and</>/<literal>&amp;&amp;</>,
+      <literal>or</>/<literal>||</>, <literal>xor</>/<literal>^^</>),
+      with their usual precedence and associativity,
       <link linkend="pgbench-builtin-functions">function calls</>, and
       parentheses.
      </para>
@@ -954,6 +962,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -961,6 +983,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>9</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>max(<replaceable>i</> [, <replaceable>...</> ] )</></></>
        <entry>integer</>
        <entry>maximum value</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 64c29dc..f9047e1 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -52,8 +52,22 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP XOR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
 
 /* Precedence: lowest to highest */
+
+/* logical */
+%left	XOR_OP
+%left	OR_OP
+%left	AND_OP
+%right  UNOT
+/* comparison */
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+/* bitwise */
+%left	'^' '|' '&' LS_OP RS_OP
+%right	UINV
+/* arithmetic */
 %left	'+' '-'
 %left	'*' '/' '%'
 %right	UMINUS
@@ -71,11 +85,29 @@ expr: '(' expr ')'			{ $$ = $2; }
 	| '+' expr %prec UMINUS	{ $$ = $2; }
 	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UINV	{ $$ = make_op(yyscanner, "^",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNOT	{ $$ = make_op(yyscanner, "^^",
+										   make_integer_constant(1), $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '^' expr			{ $$ = make_op(yyscanner, "^", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
+	| expr XOR_OP expr		{ $$ = make_op(yyscanner, "^^", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
@@ -177,6 +209,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +229,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"^^", 2, PGBENCH_XOR
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"^", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"if", 3, PGBENCH_IF
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 20891a3..0aef58b 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -113,6 +113,29 @@ newline			[\n]
 "*"				{ return '*'; }
 "/"				{ return '/'; }
 "%"				{ return '%'; }
+"="				{ return '='; }
+"=="			{ return '='; }  /* C version */
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; }  /* C version */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '^'; }
+"^"				{ return '^'; }    /* C version */
+"~"				{ return '~'; }
+"and"			{ return AND_OP; }
+"&&"			{ return AND_OP; } /* C version */
+"or"			{ return OR_OP; }
+"||"			{ return OR_OP; }  /* C version */
+"xor"			{ return XOR_OP; } /* should exist */
+"^^"			{ return XOR_OP; } /* should exist */
+"not" 			{ return '!'; }
+"!" 			{ return '!'; }    /* C version */
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 076fbd3..1033d56 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1070,6 +1070,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1121,6 +1134,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue	*lval = &vargs[0],
 								*rval = &vargs[1];
@@ -1154,6 +1171,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1181,6 +1214,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1221,6 +1270,56 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get there */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+		case PGBENCH_XOR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else if (func == PGBENCH_XOR)
+					setIntValue(retval, lb ^ rb);
+				else /* cannot get there */
+					Assert(0);
+
+				return true;
+			}
+
 		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1271,6 +1370,8 @@ evalFunc(TState *thread, CState *st,
 		/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double dval;
 				Assert(nargs == 1);
@@ -1280,6 +1381,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1395,6 +1501,11 @@ evalFunc(TState *thread, CState *st,
 			return true;
 		}
 
+		case PGBENCH_IF:
+			Assert(nargs == 3);
+			*retval = coerceToBool(&vargs[0]) ? vargs[1] : vargs[2];
+			return true;
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 7dcb67f..ef72984 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_XOR,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IF
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
functions.sqlapplication/x-sql; name=functions.sqlDownload
#2Simon Riggs
simon@2ndQuadrant.com
In reply to: Fabien COELHO (#1)
Re: pgbench more operators & functions

On 3 April 2016 at 06:54, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a simple patch...

The patch deadline has passed and we are in the last CF of 9.6, as I'm sure
you know.

Another minor patch on pgbench probably isn't going to help stabilise this
release, so these changes won't be available in core until late 2017 now.

Given that, please save up all your desired changes to pgbench and submit
in one go nearer the next CF. Thanks.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#3Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Simon Riggs (#2)
Re: pgbench more operators & functions

Hello Simon,

Here is a simple patch...

The patch deadline has passed and we are in the last CF of 9.6, as I'm
sure you know.

Yes I know, I'm ok with that, I was just putting stuff in the queue for
later, I was not asking for the patch to be considered right now.

Another minor patch on pgbench probably isn't going to help stabilise this
release, so these changes won't be available in core until late 2017 now.

Sure.

Given that, please save up all your desired changes to pgbench and submit
in one go nearer the next CF. Thanks.

Ok. Sorry, I did not realise that submitting stuff and recording it in a
CF should not be done now.

Maybe you should consider not opening the September CF if this is the
intent?

Also, what period "nearer to the next CF" is appropriate for sending
patches for this CF, which starts in five months?

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Michael Paquier
michael.paquier@gmail.com
In reply to: Fabien COELHO (#3)
Re: pgbench more operators & functions

On Mon, Apr 4, 2016 at 1:15 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a simple patch...

The patch deadline has passed and we are in the last CF of 9.6, as I'm
sure you know.

Yes I know, I'm ok with that, I was just putting stuff in the queue for
later, I was not asking for the patch to be considered right now.

There is nothing bad in sending a patch now. Though it is true that at
this period of the 9.6 development attention should be to focus on the
remaining patches in the CF.

Given that, please save up all your desired changes to pgbench and submit
in one go nearer the next CF. Thanks.

Ok. Sorry, I did not realise that submitting stuff and recording it in a CF
should not be done now.

Personally I have no problem if someone wants to register a patch,
however reviews on such a patch are unfair for the other existing
ones. Perhaps you got an idea and wanted to code it and thought that
it would be a good idea to send it now instead of three month later.
I'd say why not.

Maybe you should consider not opening the September CF if this is the
intent?
Also, what period "nearer to the next CF" is appropriate for sending patches
for this CF, which starts in five months?

The CF can remain open as far as it goes in my view to allow people to
add patches whenever they want, I see little point to close it and
prevent people from registering patches if they'd want to. They are
just not going to be considered for review and integration until the
next CF begins if those are new features, note that some of the
patches registered there are aimed at being bug fixes.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Simon Riggs
simon@2ndQuadrant.com
In reply to: Michael Paquier (#4)
Re: pgbench more operators & functions

On 4 April 2016 at 01:14, Michael Paquier <michael.paquier@gmail.com> wrote:

I'd say why not.

I'd say "why not wait?". Minor, non-urgent patches will definitely go
nowhere for a long time, so it gains nobody to submit now.

Submitting patches during freeze has been discouraged for many years, so
asking a long term contributor to avoid sending multiple minor patches is
in line with that.

--
Simon Riggs http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/&gt;
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#6David G. Johnston
david.g.johnston@gmail.com
In reply to: Simon Riggs (#5)
Re: pgbench more operators & functions

On Sun, Apr 3, 2016 at 10:18 PM, Simon Riggs <simon@2ndquadrant.com> wrote:

On 4 April 2016 at 01:14, Michael Paquier <michael.paquier@gmail.com>
wrote:

I'd say why not.

I'd say "why not wait?". Minor, non-urgent patches will definitely go
nowhere for a long time, so it gains nobody to submit now.

Submitting patches during freeze has been discouraged for many years, so
asking a long term contributor to avoid sending multiple minor patches is
in line with that.

The main downside I see is on the CF manager having more items to manage.
The committers should be able to prioritize and so seeing the other items,
while maybe not ideal (though they should be in a future CF period so they
shouldn't be too visible), doesn't seem that bad. What it does allow is
for lurkers or potential reviewers and developers to see what is being (has
been) worked on by others in the community. That kind of visibility seems
like it should be desired - since proving that nobody benefits from it
being published seem a bit of stretch of reason. But maybe I'm just not
close enough to the problems it causes - which ideally could be mitigated
in some form other than asking people to hold off making work public.

The main downside would be the human tendency to want to look at, comment
and/or work on these more minor items when they should be working on more
important things. That, though, seem like the opposite of saying
"non-urgent patches will definitely go nowhere for a long time" and
probably installs a level of parental involvement that is not necessarily
the community's role.

David J.

#7Andres Freund
andres@anarazel.de
In reply to: Simon Riggs (#5)
Re: pgbench more operators & functions

On 2016-04-04 06:18:47 +0100, Simon Riggs wrote:

I'd say "why not wait?". Minor, non-urgent patches will definitely go
nowhere for a long time, so it gains nobody to submit now.

Submitting patches during freeze has been discouraged for many years, so
asking a long term contributor to avoid sending multiple minor patches is
in line with that.

I don't see much point in asking people to postpone. I do think however
it can make sense to respond with something like:
Fabien, you've been submitting a lot of patches over the last
year. Thanks for the that! To keep up with the amount of incoming work
the prject relies on contributors also shouldering some review
responsibility. Please consider focusing on that, while we're working on
getting 9.6 ready.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Amit Kapila
amit.kapila16@gmail.com
In reply to: Andres Freund (#7)
Re: pgbench more operators & functions

On Mon, Apr 4, 2016 at 11:51 AM, Andres Freund <andres@anarazel.de> wrote:

On 2016-04-04 06:18:47 +0100, Simon Riggs wrote:

I'd say "why not wait?". Minor, non-urgent patches will definitely go
nowhere for a long time, so it gains nobody to submit now.

Submitting patches during freeze has been discouraged for many years, so
asking a long term contributor to avoid sending multiple minor patches

is

in line with that.

I don't see much point in asking people to postpone. I do think however
it can make sense to respond with something like:
Fabien, you've been submitting a lot of patches over the last
year. Thanks for the that! To keep up with the amount of incoming work
the prject relies on contributors also shouldering some review
responsibility. Please consider focusing on that, while we're working on
getting 9.6 ready.

+1. Extremely positive and encouraging way of involving other people.

With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

#9Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Andres Freund (#7)
Re: pgbench more operators & functions

Hello Andres,

I don't see much point in asking people to postpone. I do think however
it can make sense to respond with something like: Fabien, you've been
submitting a lot of patches over the last year. Thanks for the that! To
keep up with the amount of incoming work the prject relies on
contributors also shouldering some review responsibility. Please
consider focusing on that, while we're working on getting 9.6 ready.

Sure, I definitely agree about that.

I try to review all patches in my (small) area of (limited) expertise,
which is currently pgbench & some part of the checkpointer. I did also
minor bug fixes (eg isbn). AFAICS none of the patches lacking a reviewer
in 9.6 CF fall in these area.

Also note that while I submitted patches for the checkpointer, I ended up
reviewing your version of the patches, so somehow I was first author, then
a driving force to provoke you to do it your way, and finally a reviewer,
esp in performance testing which is a time consumming task.

I can also learn other things, but that means more time to do a useful
review. This "more" time is available for me mostly over the Summer, so
I'll try to be more useful to the community, and also learn new stuff,
then. Probably not ideal for 9.6, but it cannot be helped.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Fabien COELHO (#9)
Re: pgbench more operators & functions

Fabien COELHO wrote:

I try to review all patches in my (small) area of (limited) expertise, which
is currently pgbench & some part of the checkpointer. I did also minor bug
fixes (eg isbn). AFAICS none of the patches lacking a reviewer in 9.6 CF
fall in these area.

Also note that while I submitted patches for the checkpointer, I ended up
reviewing your version of the patches, so somehow I was first author, then a
driving force to provoke you to do it your way, and finally a reviewer,
esp in performance testing which is a time consumming task.

Please note that the checkpointer patch has two open items that perhaps
you can help with --- see https://wiki.postgresql.org/wiki/Open_Items

--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Fabien COELHO
fabien.coelho@mines-paristech.fr
In reply to: Alvaro Herrera (#10)
Re: pgbench more operators & functions

Please note that the checkpointer patch has two open items that perhaps
you can help with --- see https://wiki.postgresql.org/wiki/Open_Items

Indeed, I just looked at the commitfest, and I did not notice the other
threads.

I do not have an OSX available, but I'll have a look at the other one.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#1)
2 attachment(s)
Re: pgbench more operators & functions

Here is a simple patch which adds a bunch of operators (bitwise: & | ^ ~,
comparisons: =/== <>/!= < <= > >=, logical: and/&& or/|| xor/^^ not/!) and
functions (exp ln if) to pgbench. I've tried to be pg's SQL compatible where
appropriate.

Also attached is a simple test script.

Here is a sightly updated version.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-2.patchtext/x-diff; name=pgbench-more-ops-funcs-2.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index f3afedb..ea319f6 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -821,9 +821,17 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
+      arithmetic operators (unary: <literal>+</>, <literal>-</>; binary:
+      <literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>, <literal>%</>),
+      bitwise operators (unary: <literal>~</>; binary: <literal>&amp;</>,
+      <literal>|</>, <literal>#</>/<literal>^</>, <literal>&lt;&lt;</>,
+      <literal>&gt;&gt;</>)
+      comparisons (<literal>=</>/<literal>==</>, <literal>&lt;&gt;</>/<literal>!=</>,
+      <literal>&lt;=</>, <literal>&lt;</>, <literal>&gt;=</>, <literal>&gt;</>),
+      logical operators (unary: <literal>not</>/<literal>!</>;
+      binary: <literal>and</>/<literal>&amp;&amp;</>,
+      <literal>or</>/<literal>||</>, <literal>xor</>/<literal>^^</>),
+      with their usual precedence and associativity,
       <link linkend="pgbench-builtin-functions">function calls</>, and
       parentheses.
      </para>
@@ -955,6 +963,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -962,6 +977,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5</></>
       </row>
       <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -976,6 +998,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 0cc665b..233de9c 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -52,8 +52,22 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP XOR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
 
 /* Precedence: lowest to highest */
+
+/* logical */
+%left	XOR_OP
+%left	OR_OP
+%left	AND_OP
+%right  UNOT
+/* comparison */
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+/* bitwise */
+%left	'^' '|' '&' LS_OP RS_OP
+%right	UINV
+/* arithmetic */
 %left	'+' '-'
 %left	'*' '/' '%'
 %right	UMINUS
@@ -71,11 +85,29 @@ expr: '(' expr ')'			{ $$ = $2; }
 	| '+' expr %prec UMINUS	{ $$ = $2; }
 	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UINV	{ $$ = make_op(yyscanner, "^",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNOT	{ $$ = make_op(yyscanner, "^^",
+										   make_integer_constant(1), $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '^' expr			{ $$ = make_op(yyscanner, "^", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
+	| expr XOR_OP expr		{ $$ = make_op(yyscanner, "^^", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
@@ -177,6 +209,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +229,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"^^", 2, PGBENCH_XOR
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"^", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"if", 3, PGBENCH_IF
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 20891a3..0aef58b 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -113,6 +113,29 @@ newline			[\n]
 "*"				{ return '*'; }
 "/"				{ return '/'; }
 "%"				{ return '%'; }
+"="				{ return '='; }
+"=="			{ return '='; }  /* C version */
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; }  /* C version */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '^'; }
+"^"				{ return '^'; }    /* C version */
+"~"				{ return '~'; }
+"and"			{ return AND_OP; }
+"&&"			{ return AND_OP; } /* C version */
+"or"			{ return OR_OP; }
+"||"			{ return OR_OP; }  /* C version */
+"xor"			{ return XOR_OP; } /* should exist */
+"^^"			{ return XOR_OP; } /* should exist */
+"not" 			{ return '!'; }
+"!" 			{ return '!'; }    /* C version */
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 87fb006..d742e2f 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1189,6 +1189,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1240,6 +1253,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1275,6 +1292,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1303,6 +1336,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1343,7 +1392,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get there */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+		case PGBENCH_XOR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else if (func == PGBENCH_XOR)
+					setIntValue(retval, lb ^ rb);
+				else /* cannot get there */
+					Assert(0);
+
+				return true;
+			}
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1397,6 +1496,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1407,6 +1508,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1558,6 +1664,12 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IF:
+			/* should it do a lazy evaluation of the branch? */
+			Assert(nargs == 3);
+			*retval = coerceToBool(&vargs[0]) ? vargs[1] : vargs[2];
+			return true;
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index ab0f822..5306c40 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_XOR,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IF
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
functions.sqlapplication/x-sql; name=functions.sqlDownload
#13Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Fabien COELHO (#12)
Re: pgbench more operators & functions

On Sat, Jul 9, 2016 at 12:14 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a simple patch which adds a bunch of operators (bitwise: & | ^ ~,
comparisons: =/== <>/!= < <= > >=, logical: and/&& or/|| xor/^^ not/!) and
functions (exp ln if) to pgbench. I've tried to be pg's SQL compatible where
appropriate.

Also attached is a simple test script.

Here is a sightly updated version.

Hi Fabien,

I did the review of your patch and here are my views on your patch.

Purpose of the patch:
=====================

This patch introduces extensive list of new operators and functions that can be
used in expressions in pgbench transaction scripts(given with -f option).

Here is the list of operators and functions introduced by this patch:

New operators:
--------------
bitwise: <<, >>, &, |, ^/#, ~
comparisons: =/==, <>/!=, <, <=, >, >=
logical: and/&&, or/||, xor/^^, not, !

New functions:
--------------
exp, ln, if

I see there had been a discussion about timing for submission of patch, but not
much about what the patch is doing, so I believe the purpose of patch is quite
acceptable.
Currently there are limited number of operators available in pgbench. So, I
think these new operators definitely make a value addition towards custom
scripts.

Documentation:
=============
I could build the documentation without any errors.
New operators and functions are well categorized and added in proper sections
of existing pgbench documentation.

I have a small suggestion here: Earlier we had only few number of operators, so
it was OK to have the operators embedded in the description of '\set' command
itself, but now as we have much more number of operators will it be a good idea
to have a table of operators similar to that of functions. We need not have
several columns here like description, example etc., but a short table just
categorizing the operators would be sufficient.

Initial Run:
============
I was able to apply patch with 'patch -p1'.
The testcase file(functions.sql) given along the patch gives an expected output.

Further testing and review:
===========================
1. Postgres treats '^' as exponentiation rather than XOR, and '#' as XOR.
Personally, I think it can cause confusion, so it will be better if we can stick
to the behavior of Postgres mathematical operators.

2. I could not see any tests for bitwise operators in the functions.sql file
that you have attached.

3. Precedence:
a. Unary operators have more precedence than binary operators. So, UNOT and
UINV should have precedence next to UMINUS.
I tried couple of test expressions using C Vs your patch(pgbench)

expression result_in_C result_in_pgbench
(~14-14+2) -27 -3
(!14-14+2) -12 0

b. Similarly shift operators should take more precedence over other bitwise
operators:

expression result_in_C result_in_pgbench
(4|1<<1) 6 10
(4^5&3) 5 1

c. Also, comparison would take more precedence over bitwise operators(&,|,^)
but shift operators.

expression result_in_C result_in_pgbench
(2&1<3) 1 0

In backend/parser/gram.y, I see that unary operators are given higher precedence
than other operators, but it too does not have explicit precedence defined for
bitwise operators.
I tried to check Postgres documentation for operator precedence, but in
'Lexical Structure'[1]https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html the documentation does not mention anything about bitwise
operators. Also, SQL standards 99 does not talk much about operator precedence.
I may be wrong while trying to understand the precedence you defined here and
you might have picked this per some standard, but do you have any reference
which you considered for this?

4. If we are going to stick to current precedence, I think it will be good idea
to document it.

5. Sorry, I was not able to understand the "should exist" comment in following
snippet.

+"xor" { return XOR_OP; } /* should exist */
+"^^" { return XOR_OP; } /* should exist */

7. You may want to reword following comment:

+ else /* cannot get there */

To

+ else /* cannot get here */

8.
+ case PGBENCH_IF:
+ /* should it do a lazy evaluation of the branch? */
+ Assert(nargs == 3);
+ *retval = coerceToBool(&vargs[0]) ? vargs[1] : vargs[2];

I believe ternary operator does the lazy evaluation internally, but to be sure
you may consider rewriting this as following:

if (coerceToBool(&vargs[0]))
*retval = vargs[1]https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html;
else
*retval = vargs[2];

Conclusion:
===========
I have tested the patch and each of the operator is implemented correctly.
The only concern I have is precedence, otherwise the patch seems to be doing
what it is supposed to do.

[1]: https://www.postgresql.org/docs/9.6/static/sql-syntax-lexical.html

Regards,
Jeevan Ladhe.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Jeevan Ladhe (#13)
2 attachment(s)
Re: pgbench more operators & functions

Hello Jeevan,

I did the review of your patch and here are my views on your patch.

Thanks for this detailed review and debugging!

Documentation: [...] it be a good idea to have a table of operators
similar to that of functions. We need not have several columns here like
description, example etc., but a short table just categorizing the
operators would be sufficient.

Ok, done.

Further testing and review:
===========================
1. Postgres treats '^' as exponentiation rather than XOR, and '#' as XOR.
Personally, I think it can cause confusion, so it will be better if we can stick
to the behavior of Postgres mathematical operators.

Ok. I agree to avoid '^'.

2. I could not see any tests for bitwise operators in the functions.sql
file that you have attached.

Indeed. Included in attached version.

3. Precedence: [...]

Hmm. I got them all wrong, shame on me! I've followed C rules in the
updated version.

5. Sorry, I was not able to understand the "should exist" comment in following
snippet.

+"xor" { return XOR_OP; } /* should exist */
+"^^" { return XOR_OP; } /* should exist */

There is no "logical exclusive or" operator in C nor in SQL. I do not see
why not, so I put one...

7. You may want to reword following comment: [...] there -> here

Ok, fixed twice.

8. [...] if (coerceToBool(&vargs[0])) *retval = vargs[1]; else *retval = vargs[2];

Ok.

Attached is an updated patch & test script.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-3.patchtext/x-diff; name=pgbench-more-ops-funcs-3.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 285608d..c958c1c 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -821,9 +821,8 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
       <link linkend="pgbench-builtin-functions">function calls</>, and
       parentheses.
      </para>
@@ -909,6 +908,84 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators</title>
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Operator Category</entry>
+      <entry>Result Type</entry>
+      <entry>List of Operators</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry>unary arithmetic</>
+      <entry>integer or double</>
+      <entry><literal>+</>, <literal>-</></>
+     </row>
+     <row>
+      <entry>binary arithmetic</>
+      <entry>integer or double</>
+      <entry><literal>+</>, <literal>-</>, <literal>*</>, <literal>/</></>
+     </row>
+     <row>
+      <entry>binary arithmetic</>
+      <entry>integer only</>
+      <entry><literal>%</></>
+     </row>
+     <row>
+      <entry>unary bitwise</>
+      <entry>integer</>
+      <entry><literal>~</></>
+     </row>
+     <row>
+      <entry>binary bitwise</>
+      <entry>integer</>
+      <entry><literal>&amp;</>, <literal>|</>, <literal>#</></>
+     </row>
+     <row>
+      <entry>shifts</>
+      <entry>integer</>
+      <entry><literal>&lt;&lt;</>, <literal>&gt;&gt;</></>
+     </row>
+     <row>
+      <entry>comparison</>
+      <entry>boolean (0/1)</>
+      <entry>
+       <literal>=</>/<literal>==</>, <literal>&lt;&gt;</>/<literal>!=</>,
+       <literal>&lt;</>, <literal>&lt;=</>, <literal>&gt;</>, <literal>&gt;=</>
+      </entry>
+     </row>
+     <row>
+      <entry>unary logical</>
+      <entry>boolean (0/1)</>
+      <entry><literal>not</>/<literal>!</></>
+     </row>
+     <row>
+      <entry>binary logical</>
+      <entry>boolean (0/1)</>
+      <entry>
+       <literal>and</>/<literal>&amp;&amp;</>,
+       <literal>or</>/<literal>||</>,
+       <literal>xor</>/<literal>^^</>,
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -955,6 +1032,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -962,6 +1046,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5</></>
       </row>
       <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -976,6 +1067,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 0cc665b..f8dbbaf 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -52,11 +52,21 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP XOR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+
+/* Precedence: lowest to highest, taken from C */
+%left	OR_OP
+%left	XOR_OP
+%left	AND_OP
+%left   '|'
+%left   '#'
+%left   '&'
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+%left	LS_OP RS_OP
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,14 +78,32 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UNARY	{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNARY	{ $$ = make_op(yyscanner, "^^",
+										   make_integer_constant(1), $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
+	| expr XOR_OP expr		{ $$ = make_op(yyscanner, "^^", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
@@ -177,6 +205,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +225,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"^^", 2, PGBENCH_XOR
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"if", 3, PGBENCH_IF
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 20891a3..3941d5f 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -113,6 +113,28 @@ newline			[\n]
 "*"				{ return '*'; }
 "/"				{ return '/'; }
 "%"				{ return '%'; }
+"="				{ return '='; }
+"=="			{ return '='; }  /* C version */
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; }  /* C version */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+"and"			{ return AND_OP; }
+"&&"			{ return AND_OP; } /* C version */
+"or"			{ return OR_OP; }
+"||"			{ return OR_OP; }  /* C version */
+"xor"			{ return XOR_OP; } /* should exist */
+"^^"			{ return XOR_OP; } /* should exist */
+"not" 			{ return '!'; }
+"!" 			{ return '!'; }    /* C version */
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 1fb4ae4..20a841a 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1262,6 +1262,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1313,6 +1326,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1348,6 +1365,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1376,6 +1409,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1416,7 +1465,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+		case PGBENCH_XOR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else if (func == PGBENCH_XOR)
+					setIntValue(retval, lb ^ rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1470,6 +1569,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1480,6 +1581,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1631,6 +1737,15 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IF:
+			/* should it do a lazy evaluation of the branch? */
+			Assert(nargs == 3);
+			if (coerceToBool(&vargs[0]))
+				*retval = vargs[1];
+			else
+				*retval = vargs[2];
+			return true;
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index ab0f822..5306c40 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_XOR,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IF
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
functions.sqlapplication/x-sql; name=functions.sqlDownload
#15Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Fabien COELHO (#14)
Re: pgbench more operators & functions

Hi,

The patch has correct precedence now.

Further minor comments:

1. About documentation, I think it will be good idea to arrange the
operators
table with the precedence and add a line at top: "In decreasing order of
precedence".

2. You may want to remove the comment:
+ /* should it do a lazy evaluation of the branch? */

Regards,
Jeevan Ladhe

#16Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Jeevan Ladhe (#15)
1 attachment(s)
Re: pgbench more operators & functions

Hello Jeevan.

1. About documentation, I think it will be good idea to arrange the
operators table with the precedence and add a line at top: "In
decreasing order of precedence".

Done, see attached.

2. You may want to remove the comment:
+ /* should it do a lazy evaluation of the branch? */

Ok.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-4.patchtext/x-diff; name=pgbench-more-ops-funcs-4.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 285608d..bec3228 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -821,9 +821,8 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
       <link linkend="pgbench-builtin-functions">function calls</>, and
       parentheses.
      </para>
@@ -909,6 +908,84 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Operator Category</entry>
+      <entry>Result Type</entry>
+      <entry>List of Operators</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry>binary logical</>
+      <entry>boolean (0/1)</>
+      <entry>
+       <literal>or</>/<literal>||</>,
+       <literal>xor</>/<literal>^^</>,
+       <literal>and</>/<literal>&amp;&amp;</>
+      </entry>
+     </row>
+     <row>
+      <entry>binary bitwise</>
+      <entry>integer</>
+      <entry><literal>|</>, <literal>#</>, <literal>&amp;</></>
+     </row>
+     <row>
+      <entry>comparison</>
+      <entry>boolean (0/1)</>
+      <entry>
+       <literal>=</>/<literal>==</>, <literal>&lt;&gt;</>/<literal>!=</>,
+       <literal>&lt;</>, <literal>&lt;=</>, <literal>&gt;</>, <literal>&gt;=</>
+      </entry>
+     </row>
+     <row>
+      <entry>shifts</>
+      <entry>integer</>
+      <entry><literal>&lt;&lt;</>, <literal>&gt;&gt;</></>
+     </row>
+     <row>
+      <entry>binary arithmetic</>
+      <entry>integer or double</>
+      <entry><literal>+</>, <literal>-</>, <literal>*</>, <literal>/</></>
+     </row>
+     <row>
+      <entry>binary arithmetic</>
+      <entry>integer only</>
+      <entry><literal>%</></>
+     </row>
+     <row>
+      <entry>unary logical</>
+      <entry>boolean (0/1)</>
+      <entry><literal>not</>/<literal>!</></>
+     </row>
+     <row>
+      <entry>unary bitwise</>
+      <entry>integer</>
+      <entry><literal>~</></>
+     </row>
+     <row>
+      <entry>unary arithmetic</>
+      <entry>integer or double</>
+      <entry><literal>+</>, <literal>-</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -955,6 +1032,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -962,6 +1046,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5</></>
       </row>
       <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -976,6 +1067,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 0cc665b..f8dbbaf 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -52,11 +52,21 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP XOR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+
+/* Precedence: lowest to highest, taken from C */
+%left	OR_OP
+%left	XOR_OP
+%left	AND_OP
+%left   '|'
+%left   '#'
+%left   '&'
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+%left	LS_OP RS_OP
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,14 +78,32 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UNARY	{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNARY	{ $$ = make_op(yyscanner, "^^",
+										   make_integer_constant(1), $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
+	| expr XOR_OP expr		{ $$ = make_op(yyscanner, "^^", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
@@ -177,6 +205,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +225,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"^^", 2, PGBENCH_XOR
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"if", 3, PGBENCH_IF
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 20891a3..3941d5f 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -113,6 +113,28 @@ newline			[\n]
 "*"				{ return '*'; }
 "/"				{ return '/'; }
 "%"				{ return '%'; }
+"="				{ return '='; }
+"=="			{ return '='; }  /* C version */
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; }  /* C version */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+"and"			{ return AND_OP; }
+"&&"			{ return AND_OP; } /* C version */
+"or"			{ return OR_OP; }
+"||"			{ return OR_OP; }  /* C version */
+"xor"			{ return XOR_OP; } /* should exist */
+"^^"			{ return XOR_OP; } /* should exist */
+"not" 			{ return '!'; }
+"!" 			{ return '!'; }    /* C version */
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index d44cfda..d15c94e 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1262,6 +1262,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1313,6 +1326,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1348,6 +1365,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1376,6 +1409,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1416,7 +1465,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+		case PGBENCH_XOR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else if (func == PGBENCH_XOR)
+					setIntValue(retval, lb ^ rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1470,6 +1569,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1480,6 +1581,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1631,6 +1737,14 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IF:
+			Assert(nargs == 3);
+			if (coerceToBool(&vargs[0]))
+				*retval = vargs[1];
+			else
+				*retval = vargs[2];
+			return true;
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index ab0f822..5306c40 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_XOR,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IF
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
#17Jeevan Ladhe
jeevan.ladhe@enterprisedb.com
In reply to: Fabien COELHO (#16)
Re: pgbench more operators & functions

The following review has been posted through the commitfest application:
make installcheck-world: tested, passed
Implements feature: tested, passed
Spec compliant: not tested
Documentation: tested, passed

The patch looks good to me now.
Passing this to committer.

The new status of this patch is: Ready for Committer

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Stephen Frost
sfrost@snowman.net
In reply to: Jeevan Ladhe (#13)
Re: pgbench more operators & functions

Fabien, Jeevan,

* Jeevan Ladhe (jeevan.ladhe@enterprisedb.com) wrote:

On Sat, Jul 9, 2016 at 12:14 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a simple patch which adds a bunch of operators (bitwise: & | ^ ~,
comparisons: =/== <>/!= < <= > >=, logical: and/&& or/|| xor/^^ not/!) and
functions (exp ln if) to pgbench. I've tried to be pg's SQL compatible where
appropriate.

Also attached is a simple test script.

Here is a sightly updated version.

I did the review of your patch and here are my views on your patch.

Thanks for the review, I agree with most of your comments and the patch
looks pretty good, in general, but I had a few specific questions.

Apologies if I missed where these were discussed already, I've just
been reading the thread linked from the CF app, so if there is prior
discussion that I should read, please provide a link or Message-ID and
I'll go read the thread(s).

Purpose of the patch:
=====================

This patch introduces extensive list of new operators and functions that can be
used in expressions in pgbench transaction scripts(given with -f option).

Here is the list of operators and functions introduced by this patch:

New operators:
--------------
bitwise: <<, >>, &, |, ^/#, ~
comparisons: =/==, <>/!=, <, <=, >, >=
logical: and/&&, or/||, xor/^^, not, !

I'm not sure that we want to introduce operators '&&', '||' as logical
'and' and 'or' when those have specific meaning in PG which is different
(array overlaps and concatenation, specifically). I can certainly see
how it could be useful to be able to perform string concatenation in a
\set command in pgbench, for example..

Also, are we sure that we want to have both '=' and '==' for comparison?

In general, my gut feeling is that we should be trying to make what's
available in PG available in pgbench, though I can see an argument for
making what is available in C available in pgbench, but this seems to be
more of a mix of the two and I think we'd be better off deciding which
we want and sticking to it.

New functions:
--------------
exp, ln, if

I don't see any particular issue with the exp() and ln() functions. I
certainly understand how the if() function could be useful, but I'm not
entirely sure that the if(expression, true-result, false-result) is the
best approach. In particular, I recall cases with other languages where
that gets to be quite ugly when there are multiple levels of if/else
using that approach.

Documentation:
=============
I have a small suggestion here: Earlier we had only few number of operators, so
it was OK to have the operators embedded in the description of '\set' command
itself, but now as we have much more number of operators will it be a good idea
to have a table of operators similar to that of functions. We need not have
several columns here like description, example etc., but a short table just
categorizing the operators would be sufficient.

The table should really have a row per operator, which is what we do in
pretty much all of the core documention. In particular, it seems like
we should try to follow something like:

https://www.postgresql.org/docs/current/static/functions-math.html

Alternativly, if we decide that we really want to try and keep with how
PG works with these operators, we could simply make these operators work
like those and then refer to that page in the core docs.

The question which was brought up about having a line-continuation
(eg: '\') character would be useful, which really makes me wonder if we
shouldn't try to come up with a way to create functions in a pgbench
script that can later be used in a \set command. With such an approach,
we could have proper control structures for conditionals (if/else),
loops (while/for), etc, and not complicate the single-statement set of
operators with those constructs.

Lastly, we should really add some regression tests to pgbench. We
already have the start of a TAP test script which it looks like it
wouldn't be too hard to add on to.

Thanks!

Stephen

#19Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Stephen Frost (#18)
1 attachment(s)
Re: pgbench more operators & functions

Hello Stephen,

bitwise: <<, >>, &, |, ^/#, ~
comparisons: =/==, <>/!=, <, <=, >, >=
logical: and/&&, or/||, xor/^^, not, !

I'm not sure that we want to introduce operators '&&', '||' as logical
'and' and 'or' when those have specific meaning in PG which is different
(array overlaps and concatenation, specifically). I can certainly see
how it could be useful to be able to perform string concatenation in a
\set command in pgbench, for example..

Also, are we sure that we want to have both '=' and '==' for comparison?

The reason I added C operators is that Pg SQL has '!=' and a few others,
so once some are there I do not see why others should not be there as well
for consistency.

Now I agree that having operators which behave differently from psql to
pgbench is a bad idea.

In the attached patched I only included pg operators, plus "xor" which I
feel is missing and does not seem to harm.

[...] I certainly understand how the if() function could be useful

Indeed, some kind of "if" is needed, for instance to implement "tpc-b"
correctly.

, but I'm not entirely sure that the if(expression, true-result,
false-result) is the best approach. In particular, I recall cases with
other languages where that gets to be quite ugly when there are multiple
levels of if/else using that approach.

I think that pgbench is not a programming language, but an expression
language, which means that you have to provide a result, so you can only
have balanced if/then/else, which simplifies things a bit compared to
"full" language.

The SQL syntax for CASE is on the very heavy side and would be quite
complicated to implement in pgbench, so I rejected that and selected the
simplest possible function for the job.

Maybe the "if" function could be named something fancier to avoid possible
future clash on an eventual "if" keyword? Note that excel has an IF
function. Maybe "conditional"... However, as I do not envision pgbench
growing to a full language, I would be fine keeping "if" as it is.

The table should really have a row per operator, which is what we do in
pretty much all of the core documention. [...]

Ok for one line per operator.

The question which was brought up about having a line-continuation
(eg: '\') character would be useful,

:-) I submitted a patch for that, which got rejected and resulted in Tom
making pgbench share psql lexer. This is a good thing, although the
continuation extension was lost in the process. Also this means that now
such logic would probably be shared with psql, not necessarily a bad thing
either, but clearly material for another patch.

which really makes me wonder if we shouldn't try to come up with a way
to create functions in a pgbench script that can later be used in a \set
command.

I do not think that pgbench script is a good target to define functions,
because the script is implicitely in a very large loop which is executing
it over and over again. I do not think that it would make much sense to
redefine functions on each transaction... So my opinion is that without a
compeling use case, I would avoid such a feature, which would need some
careful thinking on the design side, and the implementation of which is
non trivial.

With such an approach, we could have proper control structures
for conditionals (if/else), loops (while/for), etc, and not complicate
the single-statement set of operators with those constructs.

If you want control, you can already use a DO & PL/pgsql script, although
it is interpreted server-side. Yet again, I'm not convince that pgbench is
material for such features, and it would take a compeling use case for me
to add such a thing. Moreover, the currently simple internal data
structures and executor would have to be profoundly reworked and extended
to handle a full language and functions.

Lastly, we should really add some regression tests to pgbench. We
already have the start of a TAP test script which it looks like it
wouldn't be too hard to add on to.

I agree that pgbench should be tested systematically. I think that this is
not limited to these functions and operators but a more general and
desirable feature, thus is really material for another patch.

Attached version changes:
- removes C operators not present in psql
- document operators one per line

--
Fabien.

Attachments:

pgbench-more-ops-funcs-5.patchtext/x-diff; name=pgbench-more-ops-funcs-5.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 285608d..c29966f 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -821,9 +821,8 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
       <link linkend="pgbench-builtin-functions">function calls</>, and
       parentheses.
      </para>
@@ -909,6 +908,171 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>or</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>xor</></>
+      <entry>logical exclusive-or</>
+      <entry><literal>4 xor 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>and</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>left shift</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>right shift</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>not</></>
+      <entry>logical NOT</>
+      <entry><literal>not 7</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -955,6 +1119,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -962,6 +1133,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5</></>
       </row>
       <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -976,6 +1154,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 0cc665b..f8dbbaf 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -52,11 +52,21 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP XOR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+
+/* Precedence: lowest to highest, taken from C */
+%left	OR_OP
+%left	XOR_OP
+%left	AND_OP
+%left   '|'
+%left   '#'
+%left   '&'
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+%left	LS_OP RS_OP
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,14 +78,32 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UNARY	{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNARY	{ $$ = make_op(yyscanner, "^^",
+										   make_integer_constant(1), $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
+	| expr XOR_OP expr		{ $$ = make_op(yyscanner, "^^", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
@@ -177,6 +205,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +225,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"^^", 2, PGBENCH_XOR
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"if", 3, PGBENCH_IF
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 20891a3..f62740f 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -112,7 +112,24 @@ newline			[\n]
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in psql */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in psql */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+"and"			{ return AND_OP; }
+"or"			{ return OR_OP; }
+"xor"			{ return XOR_OP; } /* should exist */
+"not" 			{ return '!'; }
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index d44cfda..d15c94e 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1262,6 +1262,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1313,6 +1326,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1348,6 +1365,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1376,6 +1409,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1416,7 +1465,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+		case PGBENCH_XOR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else if (func == PGBENCH_XOR)
+					setIntValue(retval, lb ^ rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1470,6 +1569,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1480,6 +1581,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1631,6 +1737,14 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IF:
+			Assert(nargs == 3);
+			if (coerceToBool(&vargs[0]))
+				*retval = vargs[1];
+			else
+				*retval = vargs[2];
+			return true;
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index ab0f822..5306c40 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_XOR,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IF
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
#20Michael Paquier
michael.paquier@gmail.com
In reply to: Fabien COELHO (#19)
Re: pgbench more operators & functions

On Sat, Oct 1, 2016 at 11:35 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Attached version changes:
- removes C operators not present in psql
- document operators one per line

Moved to next CF with same status: "Ready for committer".
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Stephen Frost
sfrost@snowman.net
In reply to: Fabien COELHO (#19)
Re: pgbench more operators & functions

Fabien,

* Fabien COELHO (coelho@cri.ensmp.fr) wrote:

bitwise: <<, >>, &, |, ^/#, ~
comparisons: =/==, <>/!=, <, <=, >, >=
logical: and/&&, or/||, xor/^^, not, !

I'm not sure that we want to introduce operators '&&', '||' as logical
'and' and 'or' when those have specific meaning in PG which is different
(array overlaps and concatenation, specifically). I can certainly see
how it could be useful to be able to perform string concatenation in a
\set command in pgbench, for example..

Also, are we sure that we want to have both '=' and '==' for comparison?

The reason I added C operators is that Pg SQL has '!=' and a few
others, so once some are there I do not see why others should not be
there as well for consistency.

Now I agree that having operators which behave differently from psql
to pgbench is a bad idea.

In the attached patched I only included pg operators, plus "xor"
which I feel is missing and does not seem to harm.

I'm pretty sure we should hold off on adding 'xor' until it's actually
in PG proper, otherwise we run a very serious risk that whatever we do
to add it in PG will be different from what you're suggesting we do here
for pgbench.

[...] I certainly understand how the if() function could be useful

Indeed, some kind of "if" is needed, for instance to implement
"tpc-b" correctly.

That's an interesting point.. Have you thought about ripping out the
built-in TPC-B-like functionality of pgbench and making that into a
script instead? Doing so would likely point out places where we need to
add functionality or cases which aren't handled very well. We'd also be
able to rip out all that code from pgbench and make it into a general
utility instead of "general utility + other stuff."

, but I'm not entirely sure that the if(expression, true-result,
false-result) is the best approach. In particular, I recall cases
with other languages where that gets to be quite ugly when there
are multiple levels of if/else using that approach.

I think that pgbench is not a programming language, but an
expression language, which means that you have to provide a result,
so you can only have balanced if/then/else, which simplifies things
a bit compared to "full" language.

The SQL syntax for CASE is on the very heavy side and would be quite
complicated to implement in pgbench, so I rejected that and selected
the simplest possible function for the job.

I'm not quite sure that I follow why you feel that CASE would be too
difficult to implement here..?

Maybe the "if" function could be named something fancier to avoid
possible future clash on an eventual "if" keyword? Note that excel
has an IF function. Maybe "conditional"... However, as I do not
envision pgbench growing to a full language, I would be fine keeping
"if" as it is.

Excel would be one of the ugly if-nesting cases that I was thinking
of, actually. I'm certainly not thrilled with the idea of modelling
anything off of it.

The table should really have a row per operator, which is what we
do in pretty much all of the core documention. [...]

Ok for one line per operator.

Thanks!

The question which was brought up about having a line-continuation
(eg: '\') character would be useful,

:-) I submitted a patch for that, which got rejected and resulted in
Tom making pgbench share psql lexer. This is a good thing, although
the continuation extension was lost in the process. Also this means
that now such logic would probably be shared with psql, not
necessarily a bad thing either, but clearly material for another
patch.

Sure, that makes sense to do independently.

which really makes me wonder if we shouldn't try to come up with a
way to create functions in a pgbench script that can later be used
in a \set command.

I do not think that pgbench script is a good target to define
functions, because the script is implicitely in a very large loop
which is executing it over and over again. I do not think that it
would make much sense to redefine functions on each transaction...
So my opinion is that without a compeling use case, I would avoid
such a feature, which would need some careful thinking on the design
side, and the implementation of which is non trivial.

If we're going to replace the built-in TPC-B functionality then we're
going to need a way to pass in an 'init' script of some kind which only
gets run once, that could certainly be where functions could be
defined. As for the use-case for functions, well, we need an if()
construct, as discussed, and having a need for loops doesn't seem too
far off from needing conditional control structures.

With such an approach, we could have proper control structures for
conditionals (if/else), loops (while/for), etc, and not complicate
the single-statement set of operators with those constructs.

If you want control, you can already use a DO & PL/pgsql script,
although it is interpreted server-side. Yet again, I'm not convince
that pgbench is material for such features, and it would take a
compeling use case for me to add such a thing. Moreover, the
currently simple internal data structures and executor would have to
be profoundly reworked and extended to handle a full language and
functions.

Doing it server-side would certainly not be the same as having it
client-side. Perhaps it'd be difficult to rework pgbench to support it,
but I do think it's something we should at least be considering and
making sure we don't wall ourselves off from implementing in the future.
Most scripting languages do support functions of one form or another.

Lastly, we should really add some regression tests to pgbench. We
already have the start of a TAP test script which it looks like it
wouldn't be too hard to add on to.

I agree that pgbench should be tested systematically. I think that
this is not limited to these functions and operators but a more
general and desirable feature, thus is really material for another
patch.

I don't agree with this- at a minimum, this patch should add regression
tests for the features that it's adding. Other tests should be added
but we do not need to wait for some full test suite to be dropped into
pgbench before adding any regression tests.

Thanks!

Stephen

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#21)
Re: pgbench more operators & functions

Stephen Frost <sfrost@snowman.net> writes:

* Fabien COELHO (coelho@cri.ensmp.fr) wrote:

In the attached patched I only included pg operators, plus "xor"
which I feel is missing and does not seem to harm.

I'm pretty sure we should hold off on adding 'xor' until it's actually
in PG proper, otherwise we run a very serious risk that whatever we do
to add it in PG will be different from what you're suggesting we do here
for pgbench.

Agreed --- we don't really want stuff in pgbench's language that's not
in regular SQL, IMO.

Indeed, some kind of "if" is needed, for instance to implement
"tpc-b" correctly.

That's an interesting point.. Have you thought about ripping out the
built-in TPC-B-like functionality of pgbench and making that into a
script instead?

It already is a script, it's just hardwired as a string constant in
pgbench.c rather than being a separate file. I think Fabien is
suggesting that it could be changed to more nearly approximate the
actual TPC-B spec, but IMO that would be a seriously bad idea because
it would invalidate all cross-version performance comparisons. We
decided years ago that the default script is what it is and we aren't
going to change it to try to match TPC-B more exactly.

The SQL syntax for CASE is on the very heavy side and would be quite
complicated to implement in pgbench, so I rejected that and selected
the simplest possible function for the job.

I'm not quite sure that I follow why you feel that CASE would be too
difficult to implement here..?

If you want simple, you could provide just a subset of CASE (ie, only
the CASE WHEN boolexpr variant). I think inventing some random new syntax
is a bad idea.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#23Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#22)
Re: pgbench more operators & functions

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

* Fabien COELHO (coelho@cri.ensmp.fr) wrote:

Indeed, some kind of "if" is needed, for instance to implement
"tpc-b" correctly.

That's an interesting point.. Have you thought about ripping out the
built-in TPC-B-like functionality of pgbench and making that into a
script instead?

It already is a script, it's just hardwired as a string constant in
pgbench.c rather than being a separate file. I think Fabien is
suggesting that it could be changed to more nearly approximate the
actual TPC-B spec, but IMO that would be a seriously bad idea because
it would invalidate all cross-version performance comparisons. We
decided years ago that the default script is what it is and we aren't
going to change it to try to match TPC-B more exactly.

If we could replicate what the hardwired script does in an external
script, keeping that as the default, and then provide a 'Closer to
TPC-B' script, then I'm all for that. If the existing "hardwired
script" is really just a string constant, then we shouldn't need to
worry about that invalidating prior runs.

The SQL syntax for CASE is on the very heavy side and would be quite
complicated to implement in pgbench, so I rejected that and selected
the simplest possible function for the job.

I'm not quite sure that I follow why you feel that CASE would be too
difficult to implement here..?

If you want simple, you could provide just a subset of CASE (ie, only
the CASE WHEN boolexpr variant). I think inventing some random new syntax
is a bad idea.

Agreed.

Thanks!

Stephen

#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#23)
Re: pgbench more operators & functions

Stephen Frost <sfrost@snowman.net> writes:

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

It already is a script, it's just hardwired as a string constant in
pgbench.c rather than being a separate file. I think Fabien is
suggesting that it could be changed to more nearly approximate the
actual TPC-B spec, but IMO that would be a seriously bad idea because
it would invalidate all cross-version performance comparisons. We
decided years ago that the default script is what it is and we aren't
going to change it to try to match TPC-B more exactly.

If we could replicate what the hardwired script does in an external
script, keeping that as the default, and then provide a 'Closer to
TPC-B' script, then I'm all for that.

I've got no objection to a more-nearly-TPC-B script as an option.
But why do you feel the need to pull the default script out into
a separate file? Seems to me that just adds maintenance complexity,
and the need for pgbench to have a notion of a library directory,
for little gain.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Stephen Frost
sfrost@snowman.net
In reply to: Tom Lane (#24)
Re: pgbench more operators & functions

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

Stephen Frost <sfrost@snowman.net> writes:

* Tom Lane (tgl@sss.pgh.pa.us) wrote:

It already is a script, it's just hardwired as a string constant in
pgbench.c rather than being a separate file. I think Fabien is
suggesting that it could be changed to more nearly approximate the
actual TPC-B spec, but IMO that would be a seriously bad idea because
it would invalidate all cross-version performance comparisons. We
decided years ago that the default script is what it is and we aren't
going to change it to try to match TPC-B more exactly.

If we could replicate what the hardwired script does in an external
script, keeping that as the default, and then provide a 'Closer to
TPC-B' script, then I'm all for that.

I've got no objection to a more-nearly-TPC-B script as an option.
But why do you feel the need to pull the default script out into
a separate file? Seems to me that just adds maintenance complexity,
and the need for pgbench to have a notion of a library directory,
for little gain.

Part of it is a feeling that we should really be 'eating our own
dogfood' when it comes to pgbench, but also that it seems to add
unnecessary C-level code to an otherwise general-purpose utility
for no particular reason except "that's how it was first written."

Perhaps that's overkill for this case and you have an interesting point
that it might require additional code in pgbench (though I'm not
completely convinced of that...), so I won't push too hard on it, but I
still think it'd be "better" to have pgbench just be the general purpose
utility and not also have some built-in thing, even if it's obvious that
it could just be a script.

Thanks!

Stephen

#26Robert Haas
robertmhaas@gmail.com
In reply to: Michael Paquier (#20)
Re: pgbench more operators & functions

On Sun, Oct 2, 2016 at 9:41 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:

On Sat, Oct 1, 2016 at 11:35 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Attached version changes:
- removes C operators not present in psql
- document operators one per line

Moved to next CF with same status: "Ready for committer".

I think it's pretty clear that this patch is not Ready for Committer,
because there's no consensus that we want this, and like Tom and
Stephen, I'd argue that there are large parts of it we don't want.
The documentation in the latest patch version mentions XOR and IF
which we definitely don't want because there is no similar thing in
SQL, but in addition to that, I don't think much of an argument has
been made that any of this is actually useful. I'm skeptical about
the notion that giving pgbench a vast repertoire of mathematical
functions is a good idea. What does that actually let us do that is
useful and not possible today?

I'm happy to see pgbench made better in a variety of ways, but I don't
really see why that particular thing is useful. Perhaps I'm just
missing something.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Stephen Frost (#21)
1 attachment(s)
Re: pgbench more operators & functions

Hello Stephen,

I'm pretty sure we should hold off on adding 'xor' [...]

So I removed "xor" in the attached version.

[...] I certainly understand how the if() function could be useful

Indeed, some kind of "if" is needed, for instance to implement
"tpc-b" correctly.

That's an interesting point.. Have you thought about ripping out the
built-in TPC-B-like functionality of pgbench and making that into a
script instead? Doing so would likely point out places where we need to
add functionality or cases which aren't handled very well. [...]

My point is not that pgbench should do tcp-b-real by default, but that it
should be possible to do it, and currently this is not possible. People
rely on what pgbench is doing so it should not be (significantly) changed,
at least in its default behavior.

I'm not quite sure that I follow why you feel that CASE would be too
difficult to implement here..?

Obviously, everything is possible, it just takes more time. I've added the
generic case expression syntax in the attached version.

[...] If we're going to replace the built-in TPC-B functionality

As stated above, I do *NOT* want to replace the existing functionality, I
just want to make pgbench able to do more in a sensible & useful way for
benchmarking.

I'm not currently planing to add user functions and control structures
outside of expressions in pgbench without a very strong user case.

[...] Perhaps it'd be difficult to rework pgbench to support it, but I
do think it's something we should at least be considering and making
sure we don't wall ourselves off from implementing in the future. Most
scripting languages do support functions of one form or another.

Sure.

Lastly, we should really add some regression tests to pgbench. We
already have the start of a TAP test script which it looks like it
wouldn't be too hard to add on to.

I agree that pgbench should be tested systematically. I think that
this is not limited to these functions and operators but a more
general and desirable feature, thus is really material for another
patch.

I don't agree with this- at a minimum, this patch should add regression
tests for the features that it's adding.

This has not been the case with the previous dozens of patches about
pgbench, but maybe it should start somewhere:-).

There was already one TAP script for pgbench (for testing concurrent oid
insertion, not really a pgbench functionnality), I added another one which
focusses on expressions.

Changes in v6:
- remove xor operator
- remove if function
- make keywords case insensitive (more like SQL)
- add generic case expression syntax
(note that the typing is the same strange one as pg, i.e.
CASE WHEN (RANDOM() > 0.5) THEN 1 ELSE 1.0 END;
can be either an int or a double, depending, this is not statically typed...
- add TAP test about pgbench expressions

--
Fabien.

Attachments:

pgbench-more-ops-funcs-6.patchtext/x-diff; name=pgbench-more-ops-funcs-6.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 285608d..75575ad 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -821,11 +821,11 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <literal>CASE</> generic conditional expressions
+      and parentheses.
      </para>
 
      <para>
@@ -909,6 +909,165 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>or</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>and</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>left shift</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>right shift</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>not</></>
+      <entry>logical NOT</>
+      <entry><literal>not 7</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -955,6 +1114,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -962,6 +1128,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5</></>
       </row>
       <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -976,6 +1149,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 0cc665b..3487958 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -24,8 +24,10 @@ static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -45,18 +47,28 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
+
+/* Precedence: lowest to highest, taken from C */
+%left	OR_OP
+%left	AND_OP
+%left   '|'
+%left   '#'
+%left   '&'
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+%left	LS_OP RS_OP
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,20 +80,45 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UNARY	{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNARY	{ $$ = make_uop(yyscanner, "!", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, NULL); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
@@ -119,6 +156,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,6 +165,13 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
  * - fname: function name
@@ -177,6 +222,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +242,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"!", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +369,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +389,15 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	if (else_part == NULL)
+		else_part = make_integer_constant(0);
+	when_then_list = make_elist(else_part, when_then_list);
+	return make_func(yyscanner, find_func(yyscanner, "!case_end"), when_then_list);
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 20891a3..ebf0fe3 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -66,6 +66,16 @@ space			[ \t\r\f\v]
 nonspace		[^ \t\r\f\v\n]
 newline			[\n]
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+
 /* Exclusive states */
 %x EXPR
 
@@ -112,11 +122,35 @@ newline			[\n]
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in psql */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in psql */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return '!'; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index d44cfda..f5d61bd 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1262,6 +1262,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1313,6 +1326,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1348,6 +1365,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1376,6 +1409,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1416,7 +1465,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			setIntValue(retval, !coerceToBool(&vargs[0]));
+			return true;
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1470,6 +1569,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1480,6 +1581,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1631,6 +1737,24 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (coerceToBool(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index ab0f822..8b2c01e 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/002_pgbench.pl b/src/bin/pgbench/t/002_pgbench.pl
new file mode 100644
index 0000000..3e58884
--- /dev/null
+++ b/src/bin/pgbench/t/002_pgbench.pl
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Test pgbench custom expressions
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+$node->safe_psql('postgres',
+	    'CREATE UNLOGGED TABLE pgbench_expr(id SERIAL PRIMARY KEY, val TEXT NOT NULL);');
+
+my $script = $node->basedir . '/pgbench_expressions';
+
+# test a number of expressions, which must produce increasing results
+my @value_exprs = (
+  # 1 .. 8: arithmetic and other operators
+  '1', '1+1', '1.5*2', '8/2', '15%10', '6.0E0', '(1<<3) - (-(-1))', '16 >> 1',
+  # 9 .. 17: various functions
+  'abs(-9)', 'least(5432, 10, 111)', 'greatest(11.0, -1, 7, 9)', 'int(12.0)',
+  'double(13)', 'int(pi() * 100)-300', 'sqrt(225)', 'ln(65536)/ln(2)',
+  'int(exp(3.0) - 3)',
+  # 18 .. 19: case
+  'CASE WHEN 1 AND 1 OR 0 THEN 18 END', 'case when not 0 then 19 else 0 end',
+  # 20: more case with comparisons
+  'CASE WHEN 4 < 5 THEN 5 END * ' .
+  ' (CASE WHEN 3 <= 3 THEN 0.9 END + CASE WHEN 1 <> 0 THEN 0.8 ELSE 0 END + ' .
+  '  CASE WHEN 1 = 1 THEN 0.2 END + CASE WHEN 123 > 12 THEN 0.4 END + ' .
+  '  CASE WHEN 12 >= 12 THEN 0.6 END + CASE WHEN 17 < 18 THEN 0.1 END + 1)',
+  # 21 .. 24: bitwise operators
+  '16 | 4 | 1', '31 # 9', '55 & 151', '~(-25)'
+  # not tested: random functions
+);
+
+for my $expr (@value_exprs)
+{
+  append_to_file($script,
+		 "\\set e $expr\nINSERT INTO pgbench_expr(val) VALUES (:e);\n");
+}
+
+$node->command_like(
+	[   qw(pgbench -n --transactions=1 --file), $script ],
+	qr{1/1},
+	'pgbench expressions');
+
+my $stdout = $node->safe_psql(
+  'postgres',
+  "SELECT COUNT(*) FILTER (WHERE id::FLOAT8 = val::FLOAT8) || '/' || COUNT(*)
+   FROM pgbench_expr;");
+
+#diag("stdout=$stdout");
+
+my $ntests = @value_exprs;
+like($stdout, qr{\b$ntests/$ntests\b}, 'pgbench expressions: results');
#28Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Robert Haas (#26)
Re: pgbench more operators & functions

Hello Robert,

I think it's pretty clear that this patch is not Ready for Committer,

As a reviewer, I do not know when to decide to put something as "ready".
My opinion is that it is a matter of the reviewer to decide. Whether some
consensus is actually reached, or whether a committer is going to disagree
later on, cannot be helped.

because there's no consensus that we want this, and like Tom and
Stephen, I'd argue that there are large parts of it we don't want.

The documentation in the latest patch version mentions XOR and IF
which we definitely don't want because there is no similar thing in
SQL,

I have removed these and put CASE WHEN THEN ELSE END instead in v6.

but in addition to that, I don't think much of an argument has
been made that any of this is actually useful.

In the TPC-B benchmark, some conditional is needed because under some
probability an account must be chosen in the *same* branch as the teller,
otherwise in the *other* branches.

I'm skeptical about the notion that giving pgbench a vast repertoire of
mathematical functions is a good idea. What does that actually let us
do that is useful and not possible today?

I do not see a vast "repertoire" of functions. There are "usual" int
operators, logical operators, and a few functions.

About the one added in this patch:

bitwise operations: I have seen some use to create a non uniform random
from a random one. Once one operator is put in, there is no reason not to
put the others...

exp & ln: could be used to tweak distributions.

conditional: see above.

I have not put trigonometric functions because I could not think of a
use in a benchmarking context.

I'm happy to see pgbench made better in a variety of ways, but I don't
really see why that particular thing is useful. Perhaps I'm just
missing something.

I'm trying to add features that are IMO useful for benchmarking.

When doing so, someone says "hay, you put a double expression, you must
put double variables". Although I can see the point of double expressions
for passing ints into some transformations, I can't see a double variable
really useful in any benchmark, but there it is, it is a side effect of
the process, and it is somehow to have orthogonal features.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#29Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Tom Lane (#24)
Re: pgbench more operators & functions

I've got no objection to a more-nearly-TPC-B script as an option.

Good, because adding a "per-spec" tpc-b as an additional builtin option is
one of my intentions, once pgbench is capable of it.

But why do you feel the need to pull the default script out into
a separate file? Seems to me that just adds maintenance complexity,
and the need for pgbench to have a notion of a library directory,
for little gain.

I tend to agree on this point. Now it could be possible to make pgbench
look for "builtin" scripts in a predefined location so that they are found
easilly, but I'm not sure there would be a significant added value wrt the
current status.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Stephen Frost
sfrost@snowman.net
In reply to: Fabien COELHO (#29)
Re: pgbench more operators & functions

Fabien,

* Fabien COELHO (coelho@cri.ensmp.fr) wrote:

I've got no objection to a more-nearly-TPC-B script as an option.

Good, because adding a "per-spec" tpc-b as an additional builtin
option is one of my intentions, once pgbench is capable of it.

I believe it would be really helpful to have the more-nearly-TPC-B
script written using these new capabilities of pgbench to see that
a) the new capabilities actually allow for this, b) there aren't other
things which are needed, c) to provide an actual use-case for these new
capabilities.

Thanks!

Stephen

#31Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Stephen Frost (#30)
Re: pgbench more operators & functions

Hello Stephen,

* Fabien COELHO (coelho@cri.ensmp.fr) wrote:

I've got no objection to a more-nearly-TPC-B script as an option.

Good, because adding a "per-spec" tpc-b as an additional builtin
option is one of my intentions, once pgbench is capable of it.

I believe it would be really helpful to have the more-nearly-TPC-B
script written using these new capabilities of pgbench to see that
a) the new capabilities actually allow for this, b) there aren't other
things which are needed, c) to provide an actual use-case for these new
capabilities.

Here are the details:

(1) The required schema is slightly different : currently the type used
for holding balances is not wide enough per the TCP-B standard, this mean
maybe having an option to do "pgbench -i --standard-tpcb" which would
generate the right schema, probably it should just change a few INTEGER to
INT8, or maybe use NUMERIC(10). I have not done such a patch yet.

(2) The benchmark specification requires the client application to get
hold of query results, which are currently discarded by pgbench, so
pgbench does not really comply. I have submitted a patch to do that, see:

https://commitfest.postgresql.org/11/669/

(3) The expression lines, especially with a CASE syntax, are quite long,
allowing continuations would be nice, I have submitted a patch to do so:

https://commitfest.postgresql.org/11/807/

(4) As stated above, conditions are needed. Given the above extensions,
the following script would work and comply in 2 round trips and uses two
tests and two integer comparisons, added by the patch under discussion.
It also needs to get hold of two results (the branch teller and the final
balance).

-- choose teller id
\set tid random(1, 10 * :scale)
-- get an account branch, used if not the same as teller
\set abid random(1; :scale - 1)
-- get an account in-branch number
\set laid random(1, 100000)
-- select amount
\set delta random(-999999, +999999)
-- let us now start the stuff
BEGIN \;
-- get the teller's branch
SELECT bid \into tbid
FROM pgbench_tellers WHERE tid = :tid ;
-- if random < 0.85 account is in teller's branch, else in a *different* branch
\set bid CASE \
WHEN random(0, 99) < 85 THEN :tbid \
ELSE :abid + (:abid < :tbid) \
END
\set aid :laid + 100000 * :bid
-- now move the money and return the final balance
UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid \;
-- Maybe it is better to use "RETURNING aid" in the previous UPDATE?
SELECT abalance \into abalance
FROM pgbench_accounts WHERE aid = :aid \;
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;

(5) The above "composite" queries (\;) do not work with -M prepared, which
means (a) either doing independent queries and getting a lot of added
latency, or better (b) make -M prepared work with composite queries, which
I have not done yet.

Basically the 3 patches under submission allow to write the above working
TPC-B script, but more patches are needed for the schema to comply and for
-M prepared to work as well. I would prefer to wait for all pieces to be
there before adding an example script. I do not think that one large patch
mixing everything makes much sense from an engineering point of view, even
if it makes sense from a feature point of view.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#32Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fabien COELHO (#31)
Re: pgbench more operators & functions

Fabien COELHO <coelho@cri.ensmp.fr> writes:

[ re TPC-B ]

(1) The required schema is slightly different : currently the type used
for holding balances is not wide enough per the TCP-B standard, this mean
maybe having an option to do "pgbench -i --standard-tpcb" which would
generate the right schema, probably it should just change a few INTEGER to
INT8, or maybe use NUMERIC(10). I have not done such a patch yet.

The whole question of the database setup is an interesting one. If we
were to do anything at all here, I'd want to see not only the table
schemas and initial population, but also the hard-wired "vacuum" logic,
somehow made not so hard-wired. I have no good ideas about that. The
SQL commands could possibly be taken from scripts, but what of all the
work that's gone into friendly progress reporting for table loading?

(2) The benchmark specification requires the client application to get
hold of query results, which are currently discarded by pgbench, so
pgbench does not really comply. I have submitted a patch to do that, see:

I find this completely bogus. The data is delivered to the client,
ie it's inside the pgbench process. What's the grounds for arguing
that something else has to happen to it?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Tom Lane (#32)
Re: pgbench more operators & functions

Hello Tom,

(2) The benchmark specification requires the client application to get
hold of query results, which are currently discarded by pgbench, so
pgbench does not really comply. I have submitted a patch to do that, see:

I find this completely bogus. The data is delivered to the client,
ie it's inside the pgbench process.

Hmmm... It is delivered to libpq, and the client discards it... In a
benchmarking context I think that this is not exactly the same:

For instance, one could implement a library protocol that would tell that
the result is ready without actually transfering the result, getting a
slight benefit by not doing so.

In order to avoid that kind of doubtful optimization, the benchmark
requires that the final balance is returned to the "test driver", which is
the client application, and not some subsystem linked to the database. I
think that this is a pretty sensible requirement.

Moreover, the teller's branch must be used in some case, not sure how to
do that without getting this value out anyway...

What's the grounds for arguing that something else has to happen to it?

In my view the "ground" is the benchmarking specification which wants to
ensure that the tester/implementers/vendors cannot cheat to get better
than deserved performance results...

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#34Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Tom Lane (#32)
Re: pgbench more operators & functions

Hello Tom,

I comment here on the first part of your remarks. I've answered the second
part in another mail.

(1) The required schema is slightly different : currently the type used
for holding balances is not wide enough per the TCP-B standard, this mean
maybe having an option to do "pgbench -i --standard-tpcb" which would
generate the right schema, probably it should just change a few INTEGER to
INT8, or maybe use NUMERIC(10). I have not done such a patch yet.

The whole question of the database setup is an interesting one.
If we were to do anything at all here, I'd want to see not only the
table schemas and initial population, but also the hard-wired "vacuum"
logic, somehow made not so hard-wired. I have no good ideas about that.
The SQL commands could possibly be taken from scripts, but what of all
the work that's gone into friendly progress reporting for table loading?

I'm unconvince by the current status, especially the default behaviors. I
think it should do a good sensible representative job, and not be a
minimum installation.

For instance, the default setup does not use foreign keys. It should be
the reverse, foreign keys should be included by default and an option
should be used to lessen the schema quality.

Also, given the heavy UPDATE nature of the pgbench test, a non 100%
default fill factor on some tables would make sense.

The "friendly progress reporting" only applies to the initial insert: the
vacuum, primary key and possibly foreign key alterations also take a
significant time but are not included in the progress report. On the one
hand that makes sense because pgbench has no clue about the progression of
these tasks, but on the other hand it means that the "friendly" stops
halfway in the setup. The default progress reporting is much too verbose
on any modern hardware, the quiet mode should be the default, or even the
only option.

Note that I'm not really planing to change any of this because it would
probably be rejected as it is a significant behavioral change, but I find
it annoying anyway.

--
Fabien

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Amit Kapila
amit.kapila16@gmail.com
In reply to: Fabien COELHO (#34)
Re: pgbench more operators & functions

On Sat, Oct 8, 2016 at 12:58 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Hello Tom,

I comment here on the first part of your remarks. I've answered the second
part in another mail.

(1) The required schema is slightly different : currently the type used
for holding balances is not wide enough per the TCP-B standard, this mean
maybe having an option to do "pgbench -i --standard-tpcb" which would
generate the right schema, probably it should just change a few INTEGER
to
INT8, or maybe use NUMERIC(10). I have not done such a patch yet.

The whole question of the database setup is an interesting one.
If we were to do anything at all here, I'd want to see not only the table
schemas and initial population, but also the hard-wired "vacuum" logic,
somehow made not so hard-wired. I have no good ideas about that. The SQL
commands could possibly be taken from scripts, but what of all the work
that's gone into friendly progress reporting for table loading?

I'm unconvince by the current status, especially the default behaviors. I
think it should do a good sensible representative job, and not be a minimum
installation.

For instance, the default setup does not use foreign keys. It should be the
reverse, foreign keys should be included by default and an option should be
used to lessen the schema quality.

Also, given the heavy UPDATE nature of the pgbench test, a non 100% default
fill factor on some tables would make sense.

FWIW, sometime back I have seen that with fill factor 80, at somewhat
moderate client counts (32) on 192 - Hyper Threaded m/c, the
performance is 20~30% better, but at higher client counts, it was same
as 100 fill factor. I think if go by your theory, one could also
argue to have non-default values autovacuum threshold parameters.
pgbench already has a parameter to specify non-default fillfactor and
I think that is sufficient for anyone to do performance testing.

--
With Regards,
Amit Kapila.
EnterpriseDB: http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#36Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Amit Kapila (#35)
Re: pgbench more operators & functions

Hello Amit.

Also, given the heavy UPDATE nature of the pgbench test, a non 100% default
fill factor on some tables would make sense.

FWIW, sometime back I have seen that with fill factor 80, at somewhat
moderate client counts (32) on 192 - Hyper Threaded m/c, the
performance is 20~30% better, but at higher client counts, it was same
as 100 fill factor.

The 20-30% figure is consistent with figures I collected 2 years ago about
fill factor on HDD, see the beginning run of:

http://blog.coelho.net/database/2014/08/23/postgresql-fillfactor-and-update.html

Although I found that the advantages is reduced after some time because
once a page has got an update it has some free space which can be taken
advantage of later on, if the space was not reclaimed by vacuum.

I cannot understand why there would be no advantage with more clients,
though...

Alas, performance testing is quite sensitive to many details:-(

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Fabien COELHO (#36)
Re: pgbench more operators & functions

On Sat, Oct 8, 2016 at 11:27 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Hello Amit.

Also, given the heavy UPDATE nature of the pgbench test, a non 100% default

fill factor on some tables would make sense.

FWIW, sometime back I have seen that with fill factor 80, at somewhat
moderate client counts (32) on 192 - Hyper Threaded m/c, the
performance is 20~30% better, but at higher client counts, it was same
as 100 fill factor.

The 20-30% figure is consistent with figures I collected 2 years ago about
fill factor on HDD, see the beginning run of:

http://blog.coelho.net/database/2014/08/23/postgresql-
fillfactor-and-update.html

Although I found that the advantages is reduced after some time because
once a page has got an update it has some free space which can be taken
advantage of later on, if the space was not reclaimed by vacuum.

I cannot understand why there would be no advantage with more clients,
though...

Alas, performance testing is quite sensitive to many details:-(

The current status of the patch and recent mail thread discussion doesn't
represent the same.

Closed in 2016-11 commitfest with "returned with feedback" status.
Please feel free to update the status once you submit the updated patch.

Regards,
Hari Babu
Fujitsu Australia

#38Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Haribabu Kommi (#37)
Re: pgbench more operators & functions

Hello Haribabu,

Alas, performance testing is quite sensitive to many details:-(

The current status of the patch and recent mail thread discussion doesn't
represent the same.

The same what?

The discussion was about a particular test in a particular setting for a
particular load, the fact that reducing the latency has a limited effect
in that case is a fact in life. I have produced other settings where the
effect was very important. The patch has no down side AFAICS.

Closed in 2016-11 commitfest with "returned with feedback" status.
Please feel free to update the status once you submit the updated patch.

Given the thread discussions, I do not understand why this "ready for
committer" patch is switched to "return with feedback", as there is
nothing actionnable, and I've done everything required to improve the
syntax and implementation, and to justify why these functions are useful.

I'm spending time to try to make something useful of pgbench, which
require a bunch of patches that work together to improve it for new use
case, including not being limited to the current set of operators.

This decision is both illogical and arbitrary.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#39Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Fabien COELHO (#38)
Re: pgbench more operators & functions

On Fri, Dec 2, 2016 at 5:28 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Hello Haribabu,

Alas, performance testing is quite sensitive to many details:-(

The current status of the patch and recent mail thread discussion doesn't

represent the same.

The same what?

The discussion was about a particular test in a particular setting for a
particular load, the fact that reducing the latency has a limited effect in
that case is a fact in life. I have produced other settings where the
effect was very important. The patch has no down side AFAICS.

Closed in 2016-11 commitfest with "returned with feedback" status.

Please feel free to update the status once you submit the updated patch.

Given the thread discussions, I do not understand why this "ready for
committer" patch is switched to "return with feedback", as there is nothing
actionnable, and I've done everything required to improve the syntax and
implementation, and to justify why these functions are useful.

I'm spending time to try to make something useful of pgbench, which
require a bunch of patches that work together to improve it for new use
case, including not being limited to the current set of operators.

This decision is both illogical and arbitrary.

Sorry for the changing the status of the patch against to the current
status.
While going through the recent mails, I thought that there is some
disagreement
from committer. Thanks for the clarification.

Updated status as follows.

Moved to next CF with "ready for committer" status.

Regards,
Hari Babu
Fujitsu Australia

#40Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Haribabu Kommi (#39)
Re: pgbench more operators & functions

Hello,

Sorry for the changing the status of the patch against to the current
status. While going through the recent mails, I thought that there is
some disagreement from committer.

If so, I'm willing to explain again why these operators are useful for
writing some benchmarks, for instance, this paragraph taken randomly from
the TPC-B specification, on page 16:

"""
The Account_ID is generated as follows:
• A random number X is generated within [0,1]
• If X<0.85 or branches = 1, a random Account_ID is selected over all <Branch_ID> accounts.
• If X>=0.85 and branches > 1, a random Account_ID is selected over all non-<Branch_ID> accounts.
"""

This extracy suggests clearly that having some comparison operators and
some ability to act upon the comparison result is required to implement
this particular benchmark, which is beyond pgbench current capabilities.

Moved to next CF with "ready for committer" status.

Ok. We'll see next time what becomes of it...

--
Fabien.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#41Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#27)
1 attachment(s)
Re: pgbench more operators & functions

Rebased version after small expression scanner change.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-7.patchtext/x-diff; name=pgbench-more-ops-funcs-7.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1eee8dc..8bb9c75 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -828,11 +828,11 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <literal>CASE</> generic conditional expressions
+      and parentheses.
      </para>
 
      <para>
@@ -917,6 +917,165 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>or</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>and</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>left shift</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>right shift</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>not</></>
+      <entry>logical NOT</>
+      <entry><literal>not 7</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1122,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -970,6 +1136,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5</></>
       </row>
       <row>
+       <entry><literal><function>if(<replaceable>c</>,<replaceable>e1</>,<replaceable>e2</>)</></></>
+       <entry>same as <replaceable>e*</></>
+       <entry>if <replaceable>c</> is not zero then <replaceable>e1</> else <replaceable>e2</></>
+       <entry><literal>if(0,1.0,2.0)</></>
+       <entry><literal>2.0</></>
+      </row>
+      <row>
        <entry><literal><function>int(<replaceable>x</>)</></></>
        <entry>integer</>
        <entry>cast to int</>
@@ -984,6 +1157,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..0055e51 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -24,8 +24,10 @@ static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -45,18 +47,28 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
+
+/* Precedence: lowest to highest, taken from C */
+%left	OR_OP
+%left	AND_OP
+%left   '|'
+%left   '#'
+%left   '&'
+%left	'=' NE_OP
+%left	'<' '>' LE_OP GE_OP
+%left	LS_OP RS_OP
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,20 +80,45 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr %prec UNARY	{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr %prec UNARY	{ $$ = make_uop(yyscanner, "!", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, NULL); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
@@ -119,6 +156,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,6 +165,13 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
  * - fname: function name
@@ -177,6 +222,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +242,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"!", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +369,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +389,15 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	if (else_part == NULL)
+		else_part = make_integer_constant(0);
+	when_then_list = make_elist(else_part, when_then_list);
+	return make_func(yyscanner, find_func(yyscanner, "!case_end"), when_then_list);
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..3a701ae 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,16 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,11 +137,35 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in psql */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in psql */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return '!'; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index f6cb5d4..f72d05f 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1266,6 +1266,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1317,6 +1330,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1369,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1413,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,7 +1469,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			setIntValue(retval, !coerceToBool(&vargs[0]));
+			return true;
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1474,6 +1573,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1585,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1635,6 +1741,24 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (coerceToBool(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 38b3af5..dbf7ec9 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/002_pgbench.pl b/src/bin/pgbench/t/002_pgbench.pl
new file mode 100644
index 0000000..3e58884
--- /dev/null
+++ b/src/bin/pgbench/t/002_pgbench.pl
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Test pgbench custom expressions
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+$node->safe_psql('postgres',
+	    'CREATE UNLOGGED TABLE pgbench_expr(id SERIAL PRIMARY KEY, val TEXT NOT NULL);');
+
+my $script = $node->basedir . '/pgbench_expressions';
+
+# test a number of expressions, which must produce increasing results
+my @value_exprs = (
+  # 1 .. 8: arithmetic and other operators
+  '1', '1+1', '1.5*2', '8/2', '15%10', '6.0E0', '(1<<3) - (-(-1))', '16 >> 1',
+  # 9 .. 17: various functions
+  'abs(-9)', 'least(5432, 10, 111)', 'greatest(11.0, -1, 7, 9)', 'int(12.0)',
+  'double(13)', 'int(pi() * 100)-300', 'sqrt(225)', 'ln(65536)/ln(2)',
+  'int(exp(3.0) - 3)',
+  # 18 .. 19: case
+  'CASE WHEN 1 AND 1 OR 0 THEN 18 END', 'case when not 0 then 19 else 0 end',
+  # 20: more case with comparisons
+  'CASE WHEN 4 < 5 THEN 5 END * ' .
+  ' (CASE WHEN 3 <= 3 THEN 0.9 END + CASE WHEN 1 <> 0 THEN 0.8 ELSE 0 END + ' .
+  '  CASE WHEN 1 = 1 THEN 0.2 END + CASE WHEN 123 > 12 THEN 0.4 END + ' .
+  '  CASE WHEN 12 >= 12 THEN 0.6 END + CASE WHEN 17 < 18 THEN 0.1 END + 1)',
+  # 21 .. 24: bitwise operators
+  '16 | 4 | 1', '31 # 9', '55 & 151', '~(-25)'
+  # not tested: random functions
+);
+
+for my $expr (@value_exprs)
+{
+  append_to_file($script,
+		 "\\set e $expr\nINSERT INTO pgbench_expr(val) VALUES (:e);\n");
+}
+
+$node->command_like(
+	[   qw(pgbench -n --transactions=1 --file), $script ],
+	qr{1/1},
+	'pgbench expressions');
+
+my $stdout = $node->safe_psql(
+  'postgres',
+  "SELECT COUNT(*) FILTER (WHERE id::FLOAT8 = val::FLOAT8) || '/' || COUNT(*)
+   FROM pgbench_expr;");
+
+#diag("stdout=$stdout");
+
+my $ntests = @value_exprs;
+like($stdout, qr{\b$ntests/$ntests\b}, 'pgbench expressions: results');
#42Robert Haas
robertmhaas@gmail.com
In reply to: Fabien COELHO (#38)
Re: pgbench more operators & functions

On Fri, Dec 2, 2016 at 1:28 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Closed in 2016-11 commitfest with "returned with feedback" status.
Please feel free to update the status once you submit the updated patch.

Given the thread discussions, I do not understand why this "ready for
committer" patch is switched to "return with feedback", as there is nothing
actionnable, and I've done everything required to improve the syntax and
implementation, and to justify why these functions are useful.

I'm spending time to try to make something useful of pgbench, which require
a bunch of patches that work together to improve it for new use case,
including not being limited to the current set of operators.

This decision is both illogical and arbitrary.

I disagree. I think his decision was probably based on this email from me:

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#42)
Re: pgbench more operators & functions

On Tue, Jan 24, 2017 at 11:32 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Dec 2, 2016 at 1:28 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Closed in 2016-11 commitfest with "returned with feedback" status.
Please feel free to update the status once you submit the updated patch.

Given the thread discussions, I do not understand why this "ready for
committer" patch is switched to "return with feedback", as there is nothing
actionnable, and I've done everything required to improve the syntax and
implementation, and to justify why these functions are useful.

I'm spending time to try to make something useful of pgbench, which require
a bunch of patches that work together to improve it for new use case,
including not being limited to the current set of operators.

This decision is both illogical and arbitrary.

I disagree. I think his decision was probably based on this email from me:

(argh, sent too soon)

/messages/by-id/CA+Tgmoa0zp4A+S+KosaV4QfDz-wA56vLpH8me86rmpsnkvWc2w@mail.gmail.com

Nobody responded to that, and I have not seen any committer say that
they are in favor of this. Against that, three committers (Tom,
Stephen, me) have taken the view that they are opposed to at least
some parts of it. No changes to the patch have resulted from those
complaints. So this is just submitting the same thing over and over
again and hoping that the fourth or fifth committer who looks at this
is going to have a different opinion than the first three, or fail to
notice the previous discussion.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#43)
Re: pgbench more operators & functions

Robert Haas <robertmhaas@gmail.com> writes:

On Fri, Dec 2, 2016 at 1:28 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

This decision is both illogical and arbitrary.

I disagree. I think his decision was probably based on this email from me:

(argh, sent too soon)
/messages/by-id/CA+Tgmoa0zp4A+S+KosaV4QfDz-wA56vLpH8me86rmpsnkvWc2w@mail.gmail.com

Nobody responded to that, and I have not seen any committer say that
they are in favor of this. Against that, three committers (Tom,
Stephen, me) have taken the view that they are opposed to at least
some parts of it. No changes to the patch have resulted from those
complaints. So this is just submitting the same thing over and over
again and hoping that the fourth or fifth committer who looks at this
is going to have a different opinion than the first three, or fail to
notice the previous discussion.

I concur that this is expanding pgbench's expression language well beyond
what anybody has shown a need for. I'm also concerned that there's an
opportunity cost here, in that the patch establishes a precedence ordering
for its new operators, which we'd have a hard time changing later. That's
significant because the proposed precedence is different from what you'd
get for similarly-named operators on the backend side. I realize that
it's trying to follow the C standard instead, but do we really want to
open that can of worms right now? That's a decision I'd much rather put
off if we need not make it now.

I'd be okay with the parts of this that duplicate existing backend syntax
and semantics, but I don't especially want to invent further than that.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45David G. Johnston
david.g.johnston@gmail.com
In reply to: Fabien COELHO (#29)
Re: pgbench more operators & functions

On Wed, Oct 5, 2016 at 2:03 AM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

I've got no objection to a more-nearly-TPC-B script as an option.

Good, because adding a "per-spec" tpc-b as an additional builtin option is
one of my intentions, once pgbench is capable of it.

​Trying to scan the thread as an interested third-party - without hacker
skills...

Maybe consider writing a full patch series that culminates with this
additional builtin option being added as the final patch - breaking out
this (and probably other) "requirements" patches ​separately to aid in
review. The presentation of just "add new stuff that seems useful" opens
too much room for isolated discussion without knowing what the big picture
requires. At worse you'll at least have a working version of a forked
pgbench that can do what you want.

As it stands right now you haven't provided enough context for this patch
and only the social difficulty of actually marking a patch rejected has
prevented its demise in its current form - because while it has interesting
ideas its added maintenance burden for -core without any in-core usage.
But it you make it the first patch in a 3-patch series that implements the
per-spec tpc-b the discussion moves away from these support functions and
into the broader framework in which they are made useful.

My $0.02

David J.

#46Tom Lane
tgl@sss.pgh.pa.us
In reply to: David G. Johnston (#45)
Re: pgbench more operators & functions

"David G. Johnston" <david.g.johnston@gmail.com> writes:

Maybe consider writing a full patch series that culminates with this
additional builtin option being added as the final patch - breaking out
this (and probably other) "requirements" patches separately to aid in
review. The presentation of just "add new stuff that seems useful" opens
too much room for isolated discussion without knowing what the big picture
requires. At worse you'll at least have a working version of a forked
pgbench that can do what you want.

As it stands right now you haven't provided enough context for this patch
and only the social difficulty of actually marking a patch rejected has
prevented its demise in its current form - because while it has interesting
ideas its added maintenance burden for -core without any in-core usage.
But it you make it the first patch in a 3-patch series that implements the
per-spec tpc-b the discussion moves away from these support functions and
into the broader framework in which they are made useful.

I think Fabien already did post something of the sort, or at least
discussion towards it, and there was immediately objection as to whether
his idea of TPC-B compliance was actually right. I remember complaining
that he had a totally artificial idea of what "fetching a data value"
requires. So a full patch series would be a good idea in that we could
discuss whether the requirements are correct before we get into the nitty
gritty of implementing them.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#44)
Re: pgbench more operators & functions

On Tue, Jan 24, 2017 at 12:58 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'd be okay with the parts of this that duplicate existing backend syntax
and semantics, but I don't especially want to invent further than that.

I agree, and I think that's pretty much what we all said back in
October, and the patch hasn't been revised since then to match those
comments. Perhaps I'm in a grumpy mood today, but I've got enough
patches to review from people who are willing to revise their patches
in response to feedback to spend very much time on patches from people
who aren't. I realize that it can be frustrating to have to defer to
a committer when it's a 1-1 tie between you and the person with git
access - is that really a consensus? But in this case, 3 separate
committers weighed in on this thread to very politely give essentially
the same feedback and that is certainly a consensus. Not only was the
patch not revised, but the very idea that the patch might need to be
revised before further consideration was greeted with indignation.

Let's mark this Returned with Feedback and move on. We've only got a
week left in the CommitFest anyhow and there are lots of other things
that still need work (and which actually have been revised to match
previous feedback).

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Michael Paquier
michael.paquier@gmail.com
In reply to: Robert Haas (#47)
Re: pgbench more operators & functions

On Wed, Jan 25, 2017 at 6:38 AM, Robert Haas <robertmhaas@gmail.com> wrote:

Let's mark this Returned with Feedback and move on. We've only got a
week left in the CommitFest anyhow and there are lots of other things
that still need work (and which actually have been revised to match
previous feedback).

Done as such, let's move on.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Robert Haas (#43)
Re: pgbench more operators & functions

I'm spending time to try to make something useful of pgbench, which
require a bunch of patches that work together to improve it for new
use case, including not being limited to the current set of operators.

This decision is both illogical and arbitrary.

I disagree. I think his decision was probably based on this email from me:

/messages/by-id/CA+Tgmoa0zp4A+S+KosaV4QfDz-wA56vLpH8me86rmpsnkvWc2w@mail.gmail.com

Nobody responded to that,

The answer is on the same day a direct reply that you can check here:

/messages/by-id/alpine.DEB.2.20.1610041941150.24533@lancre

The short version is: I have removed XOR and replaced "if" with the SQL
CASE syntax, and provided justification for the added operators in a
benchmarking context, i.e. some kind of test is necessary for TPC-B 2.0.0.
For conditions, logical expressions are needed. Bitwise operators are used
to skew distribution in some benchmarks (TPC-C as I recall). Functions
ln/exp could be used for the same purpose, but I can remove those two if
this is a blocker.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#50Fabien COELHO
fabien.coelho@mines-paristech.fr
In reply to: Tom Lane (#44)
2 attachment(s)
Re: pgbench more operators & functions

Hello Tom,

I concur that this is expanding pgbench's expression language well beyond
what anybody has shown a need for.

As for the motivation, I'm assuming that pgbench should provide features
necessary to implement benchmarks, so I'm adding operators that appear in
standard benchmark specifications.

From TPC-B 2.0.0 section 5.3.5:

| The Account_ID is generated as follows:
| • A random number X is generated within [0,1]
| • If X<0.85 or branches = 1, a random Account_ID is selected over all
| <Branch_ID> accounts.
| • If X>=0.85 and branches > 1, a random Account_ID is selected over all
| non-<Branch_ID> accounts.

The above uses a condition (If), logic (or, and), comparisons (=, <, >=).

From TPC-C 5.11 section 2.1.6, a bitwise-or operator is used to skew a
distribution:

| NURand (A, x, y) = (((random (0, A) | random (x, y)) + C) % (y - x + 1)) + x

And from section 5.2.5.4 of same, some time is computed based on a
logarithm:

| Tt = -log(r) * µ

I'm also concerned that there's an opportunity cost here, in that the
patch establishes a precedence ordering for its new operators, which
we'd have a hard time changing later. That's significant because the
proposed precedence is different from what you'd get for similarly-named
operators on the backend side. I realize that it's trying to follow the
C standard instead,

Oops. I just looked at the precedence from a C parser, without realizing
that precedence there was different from postgres SQL implementation:-(
This is a bug on my part.

I'd be okay with the parts of this that duplicate existing backend syntax
and semantics, but I don't especially want to invent further than that.

Okay. In the two latest versions sent, discrepancies from that were bugs,
I was trying to conform.

Version 8 attached hopefully fixes the precedence issue raised above:

- use precedence taken from "backend/gram.y" instead of C. I'm not sure
that it is wise that pg has C-like operators with a different
precedence, but this is probably much too late...

And fixes the documentation:

- remove a non existing anymore "if" function documentation which made
Robert assume that I had not taken the hint to remove it. I had!

- reorder operator documentation by their pg SQL precedence.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-8.patchtext/x-diff; name=pgbench-more-ops-funcs-8.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1eee8dc..73101e1 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -828,11 +828,11 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <literal>CASE</> generic conditional expressions
+      and parentheses.
      </para>
 
      <para>
@@ -917,6 +917,165 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1122,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -984,6 +1150,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..0f0bdd6 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -24,8 +24,10 @@ static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -45,18 +47,25 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
+
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  '!'
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,20 +77,45 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr 				{ $$ = make_uop(yyscanner, "!", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, NULL); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
@@ -119,6 +153,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,6 +162,13 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
  * - fname: function name
@@ -177,6 +219,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +239,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"!", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +366,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +386,15 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	if (else_part == NULL)
+		else_part = make_integer_constant(0);
+	when_then_list = make_elist(else_part, when_then_list);
+	return make_func(yyscanner, find_func(yyscanner, "!case_end"), when_then_list);
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..3a701ae 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,16 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,11 +137,35 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in psql */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in psql */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return '!'; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index f6cb5d4..f72d05f 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1266,6 +1266,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1317,6 +1330,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1369,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1413,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,7 +1469,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			setIntValue(retval, !coerceToBool(&vargs[0]));
+			return true;
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1474,6 +1573,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1585,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1635,6 +1741,24 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (coerceToBool(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 38b3af5..dbf7ec9 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/002_pgbench.pl b/src/bin/pgbench/t/002_pgbench.pl
new file mode 100644
index 0000000..3e58884
--- /dev/null
+++ b/src/bin/pgbench/t/002_pgbench.pl
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Test pgbench custom expressions
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+$node->safe_psql('postgres',
+	    'CREATE UNLOGGED TABLE pgbench_expr(id SERIAL PRIMARY KEY, val TEXT NOT NULL);');
+
+my $script = $node->basedir . '/pgbench_expressions';
+
+# test a number of expressions, which must produce increasing results
+my @value_exprs = (
+  # 1 .. 8: arithmetic and other operators
+  '1', '1+1', '1.5*2', '8/2', '15%10', '6.0E0', '(1<<3) - (-(-1))', '16 >> 1',
+  # 9 .. 17: various functions
+  'abs(-9)', 'least(5432, 10, 111)', 'greatest(11.0, -1, 7, 9)', 'int(12.0)',
+  'double(13)', 'int(pi() * 100)-300', 'sqrt(225)', 'ln(65536)/ln(2)',
+  'int(exp(3.0) - 3)',
+  # 18 .. 19: case
+  'CASE WHEN 1 AND 1 OR 0 THEN 18 END', 'case when not 0 then 19 else 0 end',
+  # 20: more case with comparisons
+  'CASE WHEN 4 < 5 THEN 5 END * ' .
+  ' (CASE WHEN 3 <= 3 THEN 0.9 END + CASE WHEN 1 <> 0 THEN 0.8 ELSE 0 END + ' .
+  '  CASE WHEN 1 = 1 THEN 0.2 END + CASE WHEN 123 > 12 THEN 0.4 END + ' .
+  '  CASE WHEN 12 >= 12 THEN 0.6 END + CASE WHEN 17 < 18 THEN 0.1 END + 1)',
+  # 21 .. 24: bitwise operators
+  '16 | 4 | 1', '31 # 9', '55 & 151', '~(-25)'
+  # not tested: random functions
+);
+
+for my $expr (@value_exprs)
+{
+  append_to_file($script,
+		 "\\set e $expr\nINSERT INTO pgbench_expr(val) VALUES (:e);\n");
+}
+
+$node->command_like(
+	[   qw(pgbench -n --transactions=1 --file), $script ],
+	qr{1/1},
+	'pgbench expressions');
+
+my $stdout = $node->safe_psql(
+  'postgres',
+  "SELECT COUNT(*) FILTER (WHERE id::FLOAT8 = val::FLOAT8) || '/' || COUNT(*)
+   FROM pgbench_expr;");
+
+#diag("stdout=$stdout");
+
+my $ntests = @value_exprs;
+like($stdout, qr{\b$ntests/$ntests\b}, 'pgbench expressions: results');
functions.sqlapplication/x-sql; name=functions.sqlDownload
#51Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Robert Haas (#47)
Re: pgbench more operators & functions

I agree, and I think that's pretty much what we all said back in
October, and the patch hasn't been revised since then to match those
comments.

Hmmm. It really had been revised, although I forgot to remove the "if" doc
but I had remove the if function.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Tom Lane (#46)
Re: pgbench more operators & functions

As it stands right now you haven't provided enough context for this patch
and only the social difficulty of actually marking a patch rejected has
prevented its demise in its current form - because while it has interesting
ideas its added maintenance burden for -core without any in-core usage.
But it you make it the first patch in a 3-patch series that implements the
per-spec tpc-b the discussion moves away from these support functions and
into the broader framework in which they are made useful.

I think Fabien already did post something of the sort, or at least
discussion towards it,

Yep.

and there was immediately objection as to whether his idea of TPC-B
compliance was actually right. I remember complaining that he had a
totally artificial idea of what "fetching a data value" requires.

Yep.

I think that the key misunderstanding is that you are honest and assume
that other people are honest too. This is naᅵve: There is a long history
of vendors creatively "cheating" to get better than deserve benchmark
results. Benchmark specifications try to prevent such behaviors by laying
careful requirements and procedures.

In this instance, you "know" that when pg has returned the result of the
query the data is actually on the client side, so you considered it is
fetched. That is fine for you, but from a benchmarking perspective with
external auditors your belief is not good enough.

For instance, the vendor could implement a new version of the protocol
where the data are only transfered on demand, and the result just tells
that the data is indeed somewhere on the server (eg on "SELECT abalance"
it could just check that the key exists, no need to actually fetch the
data from the table, so no need to read the table, the index is
enough...). That would be pretty stupid for real application performance,
but the benchmark would could get better tps by doing so.

Without even intentionnaly cheating, this could be part of a useful
"streaming mode" protocol option which make sense for very large results
but would be activated for a small result.

Another point is that decoding the message may be a little expensive, so
that by not actually extracting the data into the client but just keeping
it in the connection/OS one gets better performance.

Thus, TPC-B 2.0.0 benchmark specification says:

"1.3.2 Each transaction shall return to the driver the Account_Balance
resulting from successful commit of the transaction.

Comment: It is the intent of this clause that the account balance in the
database be returned to the driver, i.e., that the application retrieve
the account balance."

For me the correct interpretation of "the APPLICATION retrieve the account
balance" is that the client application code, pgbench in this context, did
indeed get the value from the vendor code, here "libpq" which is handling
the connection.

Having the value discarded from libpq by calling PQclear instead of
PQntuples/PQgetvalue/... skips a key part of the client code that no real
application would skip. This looks strange and is not representative of
real client code: as a potential auditor, because of this I would not
check the corresponding item in the audit check list:

"11.3.1.2 Verify that transaction inputs and outputs satisfy Clause 1.3."

So the benchmark implementation would not be validated.

Another trivial reason to be able to actually retrieve data is that for
benchmarking purpose it is very easy to want to test a scenario where you
want to do different things based on data received, which imply that the
data can be manipulated somehow on the benchmarking client side, which is
currently not possible.

--
Fabien.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#53Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#52)
2 attachment(s)
Re: pgbench more operators & functions

<Oops, resent, wrong from again, I must really do something about my 1000
mail addresses>

Hello Tom,

I concur that this is expanding pgbench's expression language well beyond
what anybody has shown a need for.

As for the motivation, I'm assuming that pgbench should provide features
necessary to implement benchmarks, so I'm adding operators that appear in
standard benchmark specifications.

From TPC-B 2.0.0 section 5.3.5:

| The Account_ID is generated as follows:
| • A random number X is generated within [0,1]
| • If X<0.85 or branches = 1, a random Account_ID is selected over all
| <Branch_ID> accounts.
| • If X>=0.85 and branches > 1, a random Account_ID is selected over all
| non-<Branch_ID> accounts.

The above uses a condition (If), logic (or, and), comparisons (=, <, >=).

From TPC-C 5.11 section 2.1.6, a bitwise-or operator is used to skew a
distribution:

| NURand (A, x, y) = (((random (0, A) | random (x, y)) + C) % (y - x + 1)) + x

And from section 5.2.5.4 of same, some time is computed based on a logarithm:

| Tt = -log(r) * µ

I'm also concerned that there's an opportunity cost here, in that the patch
establishes a precedence ordering for its new operators, which we'd have a
hard time changing later. That's significant because the proposed precedence
is different from what you'd get for similarly-named operators on the backend
side. I realize that it's trying to follow the C standard instead,

Oops. I just looked at the precedence from a C parser, without realizing that
precedence there was different from postgres SQL implementation:-( This is a
bug on my part.

I'd be okay with the parts of this that duplicate existing backend syntax
and semantics, but I don't especially want to invent further than that.

Okay. In the two latest versions sent, discrepancies from that were bugs, I was
trying to conform.

Version 8 attached hopefully fixes the precedence issue raised above:

- use precedence taken from "backend/gram.y" instead of C. I'm not sure
that it is wise that pg has C-like operators with a different
precedence, but this is probably much too late...

And fixes the documentation:

- remove a non existing anymore "if" function documentation which made
Robert assume that I had not taken the hint to remove it. I had!

- reorder operator documentation by their pg SQL precedence.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-8.patchtext/x-diff; charset=us-ascii; name=pgbench-more-ops-funcs-8.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1eee8dc..73101e1 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -828,11 +828,11 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <literal>CASE</> generic conditional expressions
+      and parentheses.
      </para>
 
      <para>
@@ -917,6 +917,165 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1122,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -984,6 +1150,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..0f0bdd6 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -24,8 +24,10 @@ static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -45,18 +47,25 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
 %type <str> VARIABLE FUNCTION
 
 %token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token AND_OP OR_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
+
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  '!'
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,20 +77,45 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| '!' expr 				{ $$ = make_uop(yyscanner, "!", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
 	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "&&", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "||", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, NULL); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
@@ -119,6 +153,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,6 +162,13 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
  * - fname: function name
@@ -177,6 +219,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +239,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"&&", 2, PGBENCH_AND
+	},
+	{
+		"||", 2, PGBENCH_OR
+	},
+	{
+		"!", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +366,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +386,15 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	if (else_part == NULL)
+		else_part = make_integer_constant(0);
+	when_then_list = make_elist(else_part, when_then_list);
+	return make_func(yyscanner, find_func(yyscanner, "!case_end"), when_then_list);
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..3a701ae 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,16 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,11 +137,35 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in psql */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in psql */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return '!'; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index f6cb5d4..f72d05f 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -1266,6 +1266,19 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 	}
 }
 
+static bool coerceToBool(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_DOUBLE)
+	{
+		return pval->u.dval != 0.0;
+	}
+	else
+	{
+		Assert(pval->type == PGBT_INT);
+		return pval->u.ival != 0;
+	}
+}
+
 /* assign an integer value */
 static void
 setIntValue(PgBenchValue *pv, int64 ival)
@@ -1317,6 +1330,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1369,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1413,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setIntValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setIntValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setIntValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setIntValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,7 +1469,57 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
-			/* no arguments */
+		/* integer operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+		/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				lb = coerceToBool(&vargs[0]);
+				rb = coerceToBool(&vargs[1]);
+
+				if (func == PGBENCH_AND)
+					setIntValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setIntValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			setIntValue(retval, !coerceToBool(&vargs[0]));
+			return true;
+
+		/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
 			return true;
@@ -1474,6 +1573,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1585,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1635,6 +1741,24 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (coerceToBool(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 38b3af5..dbf7ec9 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -73,9 +73,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/002_pgbench.pl b/src/bin/pgbench/t/002_pgbench.pl
new file mode 100644
index 0000000..3e58884
--- /dev/null
+++ b/src/bin/pgbench/t/002_pgbench.pl
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Test pgbench custom expressions
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+$node->safe_psql('postgres',
+	    'CREATE UNLOGGED TABLE pgbench_expr(id SERIAL PRIMARY KEY, val TEXT NOT NULL);');
+
+my $script = $node->basedir . '/pgbench_expressions';
+
+# test a number of expressions, which must produce increasing results
+my @value_exprs = (
+  # 1 .. 8: arithmetic and other operators
+  '1', '1+1', '1.5*2', '8/2', '15%10', '6.0E0', '(1<<3) - (-(-1))', '16 >> 1',
+  # 9 .. 17: various functions
+  'abs(-9)', 'least(5432, 10, 111)', 'greatest(11.0, -1, 7, 9)', 'int(12.0)',
+  'double(13)', 'int(pi() * 100)-300', 'sqrt(225)', 'ln(65536)/ln(2)',
+  'int(exp(3.0) - 3)',
+  # 18 .. 19: case
+  'CASE WHEN 1 AND 1 OR 0 THEN 18 END', 'case when not 0 then 19 else 0 end',
+  # 20: more case with comparisons
+  'CASE WHEN 4 < 5 THEN 5 END * ' .
+  ' (CASE WHEN 3 <= 3 THEN 0.9 END + CASE WHEN 1 <> 0 THEN 0.8 ELSE 0 END + ' .
+  '  CASE WHEN 1 = 1 THEN 0.2 END + CASE WHEN 123 > 12 THEN 0.4 END + ' .
+  '  CASE WHEN 12 >= 12 THEN 0.6 END + CASE WHEN 17 < 18 THEN 0.1 END + 1)',
+  # 21 .. 24: bitwise operators
+  '16 | 4 | 1', '31 # 9', '55 & 151', '~(-25)'
+  # not tested: random functions
+);
+
+for my $expr (@value_exprs)
+{
+  append_to_file($script,
+		 "\\set e $expr\nINSERT INTO pgbench_expr(val) VALUES (:e);\n");
+}
+
+$node->command_like(
+	[   qw(pgbench -n --transactions=1 --file), $script ],
+	qr{1/1},
+	'pgbench expressions');
+
+my $stdout = $node->safe_psql(
+  'postgres',
+  "SELECT COUNT(*) FILTER (WHERE id::FLOAT8 = val::FLOAT8) || '/' || COUNT(*)
+   FROM pgbench_expr;");
+
+#diag("stdout=$stdout");
+
+my $ntests = @value_exprs;
+like($stdout, qr{\b$ntests/$ntests\b}, 'pgbench expressions: results');
functions.sqlapplication/x-sql; name=functions.sqlDownload
#54Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Michael Paquier (#48)
Re: pgbench more operators & functions

Bonjour Michaᅵl, Hello Robert,

Let's mark this Returned with Feedback and move on. We've only got a
week left in the CommitFest anyhow and there are lots of other things
that still need work (and which actually have been revised to match
previous feedback).

Done as such, let's move on.

Hmmm.

I think that there is a misunderstanding, most of which being my fault.

I have really tried to do everything that was required from committers,
including revising the patch to match all previous feedback.

Version 6 sent on Oct 4 did include all fixes required at the time (no if,
no unusual and operators, TAP tests)... However I forgot to remove some
documentation about the removed stuff, which made Robert think that I had
not done it. I apologise for this mistake and the subsequent
misunderstanding:-(

The current v8 sent on Jan 25 should only implement existing server-side
stuff, including with the same precedence as pointed out by Tom.

So for the implementation side I really think that I have done exactly all
that was required of me by committers, although sometimes with bugs or
errors, my apology, again...

As for the motivation, which is another argument, I cannot do more than
point to actual published official benchmark specifications that do
require these functions. I'm not inventing anything or providing some
useless catalog of math functions.

If pgbench is about being seated on a bench and running postgres on your
laptop to get some heat, my mistake... I thought it was about
benchmarking, which does imply a few extra capabities.

If the overall feedback is to be undestood as "the postgres community does
not think that pgbench should be able to be used to implement benchmarks
such as TPC-B", then obviously I will stop efforts to improve it for that
purpose.

To conclude:

IMHO the relevant current status of the patch should be "Needs review" and
possibly "Move to next CF".

If the feedback is "we do not want pgbench to implement benchmarks such as
TPC-B", then indeed the proposed features are not needed and the status
should be "Rejected".

In any case, "Returned with feedback" does not really apply.

A+

--
Fabien.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Stephen Frost
sfrost@snowman.net
In reply to: Fabien COELHO (#54)
Re: pgbench more operators & functions

Fabien,

* Fabien COELHO (coelho@cri.ensmp.fr) wrote:

I think that there is a misunderstanding, most of which being my fault.

No worries, it happens. :)

I have really tried to do everything that was required from
committers, including revising the patch to match all previous
feedback.

Thanks for continuing to try to work through everything. I know it can
be a difficult process, but it's all towards a (hopefully) improved and
better PG.

Version 6 sent on Oct 4 did include all fixes required at the time
(no if, no unusual and operators, TAP tests)... However I forgot to
remove some documentation about the removed stuff, which made Robert
think that I had not done it. I apologise for this mistake and the
subsequent misunderstanding:-(

Ok, that helps clarify things. As does the rest of your email, for me,
anyway.

If pgbench is about being seated on a bench and running postgres on
your laptop to get some heat, my mistake... I thought it was about
benchmarking, which does imply a few extra capabities.

I believe we do want to improve pgbench and your changes are generally
welcome when it comes to adding useful capabilities. Your explanation
was also helpful about the specific requirements.

IMHO the relevant current status of the patch should be "Needs
review" and possibly "Move to next CF".

For my 2c, at least, while I'm definitely interested in this, it's not
nearly high enough on my plate with everything else going on to get any
attention in the next few weeks, at least.

I do think that, perhaps, this patch may deserve a bit of a break, to
allow people to come back to it with a fresh perspective, so perhaps
moving it to the next commitfest would be a good idea, in a Needs Review
state.

Thanks!

Stephen

#56Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Stephen Frost (#55)
Re: pgbench more operators & functions

Hello Stephen,

For my 2c, at least, while I'm definitely interested in this, it's not
nearly high enough on my plate with everything else going on to get any
attention in the next few weeks, at least.

Fine with me.

I do think that, perhaps, this patch may deserve a bit of a break, to
allow people to come back to it with a fresh perspective, so perhaps
moving it to the next commitfest would be a good idea, in a Needs Review
state.

Great, thanks. I'll move it if nobody objects.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Stephen Frost (#55)
2 attachment(s)
Re: pgbench more operators & functions

Hello,

For my 2c, at least, while I'm definitely interested in this, it's not
nearly high enough on my plate with everything else going on to get any
attention in the next few weeks, at least.

I do think that, perhaps, this patch may deserve a bit of a break, to
allow people to come back to it with a fresh perspective, so perhaps
moving it to the next commitfest would be a good idea, in a Needs Review
state.

So, let's try again for the next CF...

Here is a v9 which includes some more cleanup, hopefully in the expected
direction which is to make pgbench expressions behave as SQL expressions,
and I hope taking into account all other feedback as well.

CONTEXT

Pgbench has been given an expression parser (878fdcb8) which allows to use
full expressions instead of doing one-at-a-time operations. This parser
has been extended with functions (7e137f84) & double type (86c43f4e). The
first batch of functions was essentially a poc about how to add new
functions with various requirements. Pgbench default "tpcb-like" test
takes advantage of these additions to reduce the number of lines it needs.

MOTIVATION

This patch aims at providing actually useful functions for benchmarking.
The functions and operators provided here are usual basic operations. They
are not chosen randomly, but are simply taken from existing benchmarks:

In TPC-B 2.0.0 section 5.3.5 and TPC-C 5.11 section 2.5.1.2, the selection
of accounts uses a test (if ...), logical conditions (AND, OR) and
comparisons (<, =, >=, >).

In TPC-C 5.11 section 2.1.6, a bitwise or (|) is used to skew a
distribution based on two uniform distributions.

In TPC-C 5.11 section 5.2.5.4, a log function is used to determine "think
time", which can be truncated (i.e. "least" function, already in pgbench).

CONTENTS

The attached patch provides a consistent set of functions and operators
based on the above examples, with operator precedence taken from postgres
SQL parser:

- "boolean" type support is added, because it has been requested that
pgbench should be as close as SQL expressions as possible. This induced
some renaming as some functions & struct fields where named "num" because
they where expecting an int or a double, but a boolean is not really a
numeral.

- SQL comparisons (= <> < > <= >=) plus pg SQL "!=", which result in a
boolean.

- SQL logical operators (and or not) on booleans.

- SQL bitwise operators taken from pg: | & # << >> ~.

- mod SQL function as a synonymous for %.

- ln and exp SQL functions.

- SQL CASE/END conditional structure.

The patch also includes documentation and additional tap tests.
A test script is also provided.

This version is strict about typing, mimicking postgres behavior. For
instance, using an int as a boolean results in a error. It is easy to make
it more tolerant to types, which was the previous behavior before it was
suggested to follow SQL behavior.

Together with another submitted patch about retrieving query results, the
added capabilities allow to implement strictly conforming TPC-B
transactions.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-9.patchtext/x-diff; name=pgbench-more-ops-funcs-9.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1eee8dc..36a63fc 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -827,12 +827,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
       from <replaceable>expression</>.
       The expression may contain integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
+      boolean constants <literal>TRUE</> and <literal>FALSE</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <literal>CASE</> generic conditional expressions
+      and parentheses.
      </para>
 
      <para>
@@ -917,6 +918,165 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not false</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>0</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>integer bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>integer bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1123,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -984,6 +1151,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</>, <replaceable>j</>)</></></>
+       <entry>inteter</>
+       <entry>modulo</>
+       <entry><literal>mod(54, 32)</></>
+       <entry><literal>22</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..96f7120 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -21,11 +21,14 @@ PgBenchExpr *expr_parse_result;
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +43,32 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,20 +80,46 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, NULL); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
@@ -110,6 +148,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +168,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +177,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +213,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +235,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +255,45 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +382,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +402,15 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	if (else_part == NULL)
+		else_part = make_integer_constant(0);
+	when_then_list = make_elist(else_part, when_then_list);
+	return make_func(yyscanner, find_func(yyscanner, "!case_end"), when_then_list);
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..ad2eb32 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,18 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,15 +139,47 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index f6cb5d4..e5557b7 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -449,6 +448,7 @@ static const BuiltinScript builtin_script[] =
 /* Function prototypes */
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
 static void doLog(TState *thread, CState *st,
 	  StatsData *agg, bool skipped, double latency, double lag);
@@ -972,50 +972,62 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else if (var->value.type == PGBT_BOOLEAN)
+		snprintf(stringform, sizeof(stringform),
+				 "%s", var->value.u.bval ? "true" : "false");
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	if (pg_strcasecmp(var->svalue, "true") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	if (pg_strcasecmp(var->svalue, "false") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else	/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1073,7 +1085,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1099,10 +1111,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1110,7 +1122,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1119,11 +1131,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1136,7 +1148,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 static char *
@@ -1225,6 +1237,19 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1234,11 +1259,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1247,6 +1271,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1258,12 +1287,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* assign an integer value */
@@ -1282,6 +1331,14 @@ setDoubleValue(PgBenchValue *pv, double dval)
 	pv->u.dval = dval;
 }
 
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
+}
+
 /* maximum number of function arguments */
 #define MAX_FARGS 16
 
@@ -1317,6 +1374,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1413,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1457,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,6 +1513,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1460,11 +1610,12 @@ evalFunc(TState *thread, CState *st,
 
 				if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1474,6 +1625,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1637,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1562,6 +1720,28 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					bool condition;
+
+					if (!coerceToBool(&vargs[2*i], &condition))
+						return false;
+
+					if (condition)
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1671,10 +1851,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2219,7 +2399,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4127,16 +4307,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 38b3af5..37d92b5 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -34,7 +34,8 @@ union YYSTYPE;
 typedef enum
 {
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +46,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +75,24 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/002_pgbench.pl b/src/bin/pgbench/t/002_pgbench.pl
new file mode 100644
index 0000000..5c4479c
--- /dev/null
+++ b/src/bin/pgbench/t/002_pgbench.pl
@@ -0,0 +1,57 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 4;
+
+# Test pgbench custom expressions
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+$node->safe_psql('postgres',
+	    'CREATE UNLOGGED TABLE pgbench_expr(id SERIAL PRIMARY KEY, val TEXT NOT NULL);');
+
+my $script = $node->basedir . '/pgbench_expressions';
+
+# test a number of expressions, which must produce increasing results
+my @value_exprs = (
+  # 1 .. 8: constants, arithmetic and other operators
+  '1', '1+1', '1.5*2', '8/2', '15%10', '6.0E0', '(1<<3) - (-(-1))', '16 >> 1',
+  # 9 .. 17: various functions
+  'abs(-9)', 'least(5432, 10, 111)', 'greatest(11.0, -1, 7, 9)', 'int(12.0)',
+  'double(13)', 'int(pi() * 100)-300', 'sqrt(225)', 'ln(65536)/ln(2)',
+  'int(exp(3.0) - 3)',
+  # 18 .. 19: case
+  'CASE WHEN TRUE AND TRUE OR FALSE THEN 18 END',
+  'case when not false then 19 else 0 end',
+  # 20: more case with comparisons
+  'CASE WHEN 4 < 5 THEN 5 END * ' .
+  ' (CASE WHEN 3 <= 3 THEN 0.9 END + CASE WHEN 1 <> 0 THEN 0.8 ELSE 0 END + ' .
+  '  CASE WHEN 1 = 1 THEN 0.2 END + CASE WHEN 123 > 12 THEN 0.4 END + ' .
+  '  CASE WHEN 12 >= 12 THEN 0.6 END + CASE WHEN 17 < 18 THEN 0.1 END + 1)',
+  # 21 .. 24: bitwise operators
+  '16 | 4 | 1', '31 # 9', '55 & 151', '~(-25)'
+  # not tested: random functions
+);
+
+for my $expr (@value_exprs)
+{
+  append_to_file($script,
+		 "\\set e $expr\nINSERT INTO pgbench_expr(val) VALUES (:e);\n");
+}
+
+$node->command_like(
+	[   qw(pgbench -n --transactions=1 --file), $script ],
+	qr{1/1},
+	'pgbench expressions');
+
+my $stdout = $node->safe_psql(
+  'postgres',
+  "SELECT COUNT(*) FILTER (WHERE id::FLOAT8 = val::FLOAT8) || '/' || COUNT(*)
+   FROM pgbench_expr;");
+
+#diag("stdout=$stdout");
+
+my $ntests = @value_exprs;
+like($stdout, qr{\b$ntests/$ntests\b}, 'pgbench expressions: results');
functions.sqlapplication/x-sql; name=functions.sqlDownload
#58David Steele
david@pgmasters.net
In reply to: Fabien COELHO (#57)
Re: pgbench more operators & functions

On 2/4/17 4:51 AM, Fabien COELHO wrote:

Hello,

For my 2c, at least, while I'm definitely interested in this, it's not
nearly high enough on my plate with everything else going on to get any
attention in the next few weeks, at least.

I do think that, perhaps, this patch may deserve a bit of a break, to
allow people to come back to it with a fresh perspective, so perhaps
moving it to the next commitfest would be a good idea, in a Needs Review
state.

So, let's try again for the next CF...

Here is a v9 which includes some more cleanup, hopefully in the expected
direction which is to make pgbench expressions behave as SQL
expressions, and I hope taking into account all other feedback as well.

CONTEXT

Pgbench has been given an expression parser (878fdcb8) which allows to
use full expressions instead of doing one-at-a-time operations. This
parser has been extended with functions (7e137f84) & double type
(86c43f4e). The first batch of functions was essentially a poc about how
to add new functions with various requirements. Pgbench default
"tpcb-like" test takes advantage of these additions to reduce the number
of lines it needs.

MOTIVATION

This patch aims at providing actually useful functions for benchmarking.
The functions and operators provided here are usual basic operations.
They are not chosen randomly, but are simply taken from existing
benchmarks:

In TPC-B 2.0.0 section 5.3.5 and TPC-C 5.11 section 2.5.1.2, the
selection of accounts uses a test (if ...), logical conditions (AND, OR)
and comparisons (<, =, >=, >).

In TPC-C 5.11 section 2.1.6, a bitwise or (|) is used to skew a
distribution based on two uniform distributions.

In TPC-C 5.11 section 5.2.5.4, a log function is used to determine
"think time", which can be truncated (i.e. "least" function, already in
pgbench).

CONTENTS

The attached patch provides a consistent set of functions and operators
based on the above examples, with operator precedence taken from
postgres SQL parser:

- "boolean" type support is added, because it has been requested that
pgbench should be as close as SQL expressions as possible. This induced
some renaming as some functions & struct fields where named "num"
because they where expecting an int or a double, but a boolean is not
really a numeral.

- SQL comparisons (= <> < > <= >=) plus pg SQL "!=", which result in a
boolean.

- SQL logical operators (and or not) on booleans.

- SQL bitwise operators taken from pg: | & # << >> ~.

- mod SQL function as a synonymous for %.

- ln and exp SQL functions.

- SQL CASE/END conditional structure.

The patch also includes documentation and additional tap tests.
A test script is also provided.

This version is strict about typing, mimicking postgres behavior. For
instance, using an int as a boolean results in a error. It is easy to
make it more tolerant to types, which was the previous behavior before
it was suggested to follow SQL behavior.

Together with another submitted patch about retrieving query results,
the added capabilities allow to implement strictly conforming TPC-B
transactions.

This patch applies cleanly and compiles at cccbdde with some whitespace
issues.

$ patch -p1 < ../other/pgbench-more-ops-funcs-9.patch
(Stripping trailing CRs from patch.)
patching file doc/src/sgml/ref/pgbench.sgml
(Stripping trailing CRs from patch.)
patching file src/bin/pgbench/exprparse.y
(Stripping trailing CRs from patch.)
patching file src/bin/pgbench/exprscan.l
(Stripping trailing CRs from patch.)
patching file src/bin/pgbench/pgbench.c
(Stripping trailing CRs from patch.)
patching file src/bin/pgbench/pgbench.h
(Stripping trailing CRs from patch.)
patching file src/bin/pgbench/t/002_pgbench.pl

Any reviewers want to have a look?

--
-David
david@pgmasters.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Fabien COELHO
coelho@cri.ensmp.fr
In reply to: David Steele (#58)
Re: pgbench more operators & functions

Hello David,

This patch applies cleanly and compiles at cccbdde with some whitespace
issues.

$ patch -p1 < ../other/pgbench-more-ops-funcs-9.patch
(Stripping trailing CRs from patch.)

My guess is that your mailer changed the eol-style of the file when saving
it:

sh> sha1sum pg-patches/pgbench-more-ops-funcs-9.patch
608a601561f4cba982f0ce92df30d1868338342b

ISTM that standard mime-type of *.patch and *.diff is really
"text/x-diff", so my ubuntu laptop is somehow right to put that in
"/etc/mime.types", but this seems to have anoying consequences at least on
Macs.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Andres Freund
andres@anarazel.de
In reply to: David Steele (#58)
Re: pgbench more operators & functions

Hi,

On 2017-03-16 12:21:31 -0400, David Steele wrote:

Any reviewers want to have a look?

Unfortunately there wasn't much of that. I think that this patch
atm doesn't have sufficient design agreement to be considered for
v10. As the current CF has formally ended, I think we'll have to punt
this to v11.

Greetings,

Andres Freund

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#57)
1 attachment(s)
Re: pgbench more operators & functions

Here is a v9 which includes some more cleanup, hopefully in the expected
direction which is to make pgbench expressions behave as SQL
expressions, and I hope taking into account all other feedback as well.

CONTEXT

Pgbench has been given an expression parser (878fdcb8) which allows to use
full expressions instead of doing one-at-a-time operations. This parser has
been extended with functions (7e137f84) & double type (86c43f4e). The first
batch of functions was essentially a poc about how to add new functions with
various requirements. Pgbench default "tpcb-like" test takes advantage of
these additions to reduce the number of lines it needs.

MOTIVATION

This patch aims at providing actually useful functions for benchmarking. The
functions and operators provided here are usual basic operations. They are
not chosen randomly, but are simply taken from existing benchmarks:

In TPC-B 2.0.0 section 5.3.5 and TPC-C 5.11 section 2.5.1.2, the selection of
accounts uses a test (if ...), logical conditions (AND, OR) and comparisons
(<, =, >=, >).

In TPC-C 5.11 section 2.1.6, a bitwise or (|) is used to skew a distribution
based on two uniform distributions.

In TPC-C 5.11 section 5.2.5.4, a log function is used to determine "think
time", which can be truncated (i.e. "least" function, already in pgbench).

CONTENTS

The attached patch provides a consistent set of functions and operators based
on the above examples, with operator precedence taken from postgres SQL
parser:

- "boolean" type support is added, because it has been requested that pgbench
should be as close as SQL expressions as possible. This induced some renaming
as some functions & struct fields where named "num" because they where
expecting an int or a double, but a boolean is not really a numeral.

- SQL comparisons (= <> < > <= >=) plus pg SQL "!=", which result in a
boolean.

- SQL logical operators (and or not) on booleans.

- SQL bitwise operators taken from pg: | & # << >> ~.

- mod SQL function as a synonymous for %.

- ln and exp SQL functions.

- SQL CASE/END conditional structure.

The patch also includes documentation and additional tap tests.
A test script is also provided.

This version is strict about typing, mimicking postgres behavior. For
instance, using an int as a boolean results in a error. It is easy to make it
more tolerant to types, which was the previous behavior before it was
suggested to follow SQL behavior.

Together with another submitted patch about retrieving query results, the
added capabilities allow to implement strictly conforming TPC-B transactions.

Given the time scale to get things through, or eventually not,
here is an update I was initially planning to submit later on.

On top of new functions, operators and the boolean type provided in v9:

- improve boolean conversion for "yes", "on" and other variants,
in line with pg general behavior.

- add support for NULL, including IS test variants.

- conditions are quite permissive i.e. non zero numericals are true
on a test.

- TAP tests are removed.
I think there must be a TAP test, but the pgbench testing
infrastructure is currently not very adequate.
I've submitted an independent patch to enhance it:

https://commitfest.postgresql.org/14/1118/

that I suggest should be considered first. Once there is such
convenient infra, I would update this patch to take advantage of it.

- it cleans up a few things in the implementation

--
Fabien.

Attachments:

pgbench-more-ops-funcs-10.patchtext/x-diff; name=pgbench-more-ops-funcs-10.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1eee8dc..a7a1f4a 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -825,14 +825,31 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
      <para>
       Sets variable <replaceable>varname</> to a value calculated
       from <replaceable>expression</>.
-      The expression may contain integer constants such as <literal>5432</>,
+      The expression may contain the <literal>NULL</> constant,
+      boolean constants <literal>TRUE</> and <literal>FALSE</>,
+      integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <literal>CASE</> generic conditional expressions
+      and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</> on
+      <literal>NULL</> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are <literal>TRUE</>,
+      zero numerical values and <literal>NULL</> are <literal>FALSE</>.
+     </para>
+
+     <para>
+      When no final <literal>ELSE</> clause is provided to a <literal>CASE</>,
+      the default value is <literal>NULL</>.
      </para>
 
      <para>
@@ -917,6 +934,177 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not false</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</></>
+      <entry>value tests</>
+      <entry><literal>1 is null</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</></>
+      <entry>null tests</>
+      <entry><literal>1 notnull</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>integer bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>integer bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1151,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -984,6 +1179,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</>, <replaceable>j</>)</></></>
+       <entry>inteter</>
+       <entry>modulo</>
+       <entry><literal>mod(54, 32)</></>
+       <entry><literal>22</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..9f5e807 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,15 +143,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index ae36247..6d21ccc 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -447,6 +446,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -972,50 +973,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else	/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1073,7 +1106,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1099,10 +1132,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1110,7 +1143,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1119,11 +1152,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1136,7 +1169,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 static char *
@@ -1225,6 +1258,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1234,11 +1322,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1247,6 +1334,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1258,12 +1350,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1293,13 +1405,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1308,6 +1424,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1317,6 +1441,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1480,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1524,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,6 +1580,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1458,13 +1675,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1474,6 +1694,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1706,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1562,6 +1789,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1635,6 +1879,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1671,10 +1925,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2219,7 +2473,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4127,16 +4381,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 38b3af5..23536f7 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
#62Pavel Stehule
pavel.stehule@gmail.com
In reply to: Fabien COELHO (#61)
Re: pgbench more operators & functions

Hi

I am watching this patch - it looks so there are not problems. I found only
issue in documentation - new CASE expression is not documented.

Regards

Pavel

#63Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Pavel Stehule (#62)
1 attachment(s)
Re: pgbench more operators & functions

Hello Pavel,

I am watching this patch - it looks so there are not problems.

Great.

I found only issue in documentation - new CASE expression is not
documented.

Hmmm. Actually there was a rather very discreet one in v10:

+ SQL <literal>CASE</> generic conditional expressions

I've improved it in attached v11:
- add a link to the CASE full documentation
- add an example expression with CASE ...

Also, if the "pgbench - improve tap test infrastructure" patch get to be
committed, I'll add coverage for these operators and functions instead of
the external pgbench test scripts I provided.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-11.patchtext/x-diff; name=pgbench-more-ops-funcs-11.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 5735c48..af581f3 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -825,14 +825,31 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
      <para>
       Sets variable <replaceable>varname</> to a value calculated
       from <replaceable>expression</>.
-      The expression may contain integer constants such as <literal>5432</>,
+      The expression may contain the <literal>NULL</> constant,
+      boolean constants <literal>TRUE</> and <literal>FALSE</>,
+      integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <link linkend="functions-case"><token>CASE</> generic conditional
+      expressions</> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</> on
+      <literal>NULL</> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are <literal>TRUE</>,
+      zero numerical values and <literal>NULL</> are <literal>FALSE</>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</> clause is provided to a <token>CASE</>,
+      the default value is <literal>NULL</>.
      </para>
 
      <para>
@@ -841,6 +858,7 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x <> 0 THEN :y/:x ELSE NULL END 
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -917,6 +935,177 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not false</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</></>
+      <entry>value tests</>
+      <entry><literal>1 is null</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</></>
+      <entry>null tests</>
+      <entry><literal>1 notnull</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>integer bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>integer bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1152,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -984,6 +1180,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</>, <replaceable>j</>)</></></>
+       <entry>inteter</>
+       <entry>modulo</>
+       <entry><literal>mod(54, 32)</></>
+       <entry><literal>22</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..9f5e807 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,15 +143,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index ae36247..6d21ccc 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -447,6 +446,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -972,50 +973,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else	/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1073,7 +1106,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1099,10 +1132,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1110,7 +1143,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1119,11 +1152,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1136,7 +1169,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 static char *
@@ -1225,6 +1258,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1234,11 +1322,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1247,6 +1334,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1258,12 +1350,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1293,13 +1405,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1308,6 +1424,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1317,6 +1441,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1480,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1524,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,6 +1580,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1458,13 +1675,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1474,6 +1694,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1706,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1562,6 +1789,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1635,6 +1879,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1671,10 +1925,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2219,7 +2473,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4127,16 +4381,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 38b3af5..23536f7 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
#64Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#63)
Re: pgbench more operators & functions

[doc about CASE...]

I've improved it in attached v11:
- add a link to the CASE full documentation
- add an example expression with CASE ...

Do you think I should improve the doc further?

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Pavel Stehule
pavel.stehule@gmail.com
In reply to: Fabien COELHO (#64)
Re: pgbench more operators & functions

2017-05-30 7:19 GMT+02:00 Fabien COELHO <coelho@cri.ensmp.fr>:

[doc about CASE...]

I've improved it in attached v11:
- add a link to the CASE full documentation
- add an example expression with CASE ...

Do you think I should improve the doc further?

I am sorry, I didn't look there yet

The patch looks ok, but the main issue is missing regress tests. I know so
it is another patch. But it should be placed differently

1. first patch - initial infrastructure for pgbench regress tests
2. this patch + related regress tests

Regards

Pavel

Show quoted text

--
Fabien.

#66Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Pavel Stehule (#65)
Re: pgbench more operators & functions

The patch looks ok,

Ok.

but the main issue is missing regress tests.

Yes, I agree.

I know so it is another patch. But it should be placed differently

1. first patch - initial infrastructure for pgbench regress tests
2. this patch + related regress tests

Yep. I have no control about the display order, committers are too few and
overbooked, pgbench is not necessarily a priority, so I can only wait for
the non-regression test improvements to get committed some day before
updating/adding tests for the other patches in the queue (query result in
variable, utf8 variable names...).

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabien COELHO (#63)
Re: pgbench more operators & functions

On 5/24/17 03:14, Fabien COELHO wrote:

I've improved it in attached v11:
- add a link to the CASE full documentation
- add an example expression with CASE ...

This patch needs (at least) a rebase for the upcoming commit fest.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#67)
Re: pgbench more operators & functions

Hello Peter,

On 5/24/17 03:14, Fabien COELHO wrote:

I've improved it in attached v11:
- add a link to the CASE full documentation
- add an example expression with CASE ...

This patch needs (at least) a rebase for the upcoming commit fest.

Here is a rebase.

It still passes my tests.

From my point of view this patch is mostly ok, after 16 months in the
pipe.

ISTM that it is not tagged "ready" because there should be tap tests
attached. However the current tap tests in pgbench are very poor,
basically nothing at all is tested. There is a patch
(https://commitfest.postgresql.org/14/1118/) also submitted for adding a
significant tap test infrastructure, and if it gets through the idea is to
update this operator patch to cover the different new functions and
operators. It could be done in reverse order also, i.e. if the operator
patch get through the tap test patch would be updated to also test the new
functions and operators. The status of the tap-test patch is that it
really needs to be tested on Windows.

Note that someone might object that they do not need these operators and
functions in pgbench so they are useless, hence the patch should be
rejected. My answer is that standard benchmarks, eg TPC-B, actually use
these operator classes (bitwise, logical, ln...) so they are needed if
pgbench is to be used to implement said benchmarks. And if this is not
desirable, then what is the point of pgbench?

A renew interest is that "\if" commands have recently been added to
"psql", an "\if" calls for logical expressions, so a next step would be to
move the expression capability as a shared tool in front-end utils so that
it may be used both by "pgbench" and "psql". Also, I'll probably submit an
"\if" extension to pgbench backslash command language, as it is also
definitely a useful tool in a benchmark script.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#68)
1 attachment(s)
Re: pgbench more operators & functions

Here is a rebase.

Argh, sorry, missing attachement... Here it is really.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-12.patchtext/x-diff; name=pgbench-more-ops-funcs-12.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 03e1212..520daae 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -825,14 +825,31 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
      <para>
       Sets variable <replaceable>varname</> to a value calculated
       from <replaceable>expression</>.
-      The expression may contain integer constants such as <literal>5432</>,
+      The expression may contain the <literal>NULL</> constant,
+      boolean constants <literal>TRUE</> and <literal>FALSE</>,
+      integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <link linkend="functions-case"><token>CASE</> generic conditional
+      expressions</> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</> on
+      <literal>NULL</> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are <literal>TRUE</>,
+      zero numerical values and <literal>NULL</> are <literal>FALSE</>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</> clause is provided to a <token>CASE</>,
+      the default value is <literal>NULL</>.
      </para>
 
      <para>
@@ -841,6 +858,7 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x <> 0 THEN :y/:x ELSE NULL END 
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -917,6 +935,177 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not false</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</></>
+      <entry>value tests</>
+      <entry><literal>1 is null</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</></>
+      <entry>null tests</>
+      <entry><literal>1 notnull</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>integer bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>integer bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -963,6 +1152,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -984,6 +1180,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</>, <replaceable>j</>)</></></>
+       <entry>inteter</>
+       <entry>modulo</>
+       <entry><literal>mod(54, 32)</></>
+       <entry><literal>22</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index dc1367b..9f5e807 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,15 +143,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index ae78c7b..5e11701 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -447,6 +446,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -972,50 +973,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1073,7 +1106,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1099,10 +1132,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1110,7 +1143,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1119,11 +1152,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1136,7 +1169,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 static char *
@@ -1225,6 +1258,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1234,11 +1322,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1247,6 +1334,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1258,12 +1350,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1293,13 +1405,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1308,6 +1424,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1317,6 +1441,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1352,6 +1480,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1380,6 +1524,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1420,6 +1580,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1458,13 +1675,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1474,6 +1694,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1484,6 +1706,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1562,6 +1789,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1635,6 +1879,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1671,10 +1925,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2219,7 +2473,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4136,16 +4390,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index abc13e9..0492e4e 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
#70Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#69)
1 attachment(s)
Re: pgbench more operators & functions

Hello Pavel,

Here is a v13. No code changes, but TAP tests added to maintain pgbench
coverage to green.

Summary of patch contents:

This patch extends pgbench expressions syntax while keeping
compatibility with SQL expressions.

It adds support for NULL and BOOLEAN, as well as assorted logical,
comparison and test operators (AND, <>, <=, IS NULL...).

A CASE construct is provided which takes advantage of the added BOOLEAN.

Integer and double functions and operators are also extended: bitwise
operators (<< & ...), exp/ln, mod() as synonymous to % (matching pg).

Added TAP tests maintain pgbench source coverage to green (if you ignore
lexer & parser generated files...).

Future plans include extending and synchronizing psql & pgbench variable
and expression syntaxes:
- move expression parsing and evaluation in fe_utils,
which would allow to
- extend psql with some \let i <expression> cliend-side syntax
(ISTM that extending the \set syntax cannot be upward compatible)
and probably handle \let as a synonymous to \set in pgbench.
- allow \if <expression> in psql instead of just \if <boolean>
- add \if ... support to pgbench
- maybe add TEXT type support to the expression engine, if useful
- maybe add :'var" and :"var" support to pgbench, if useful

There are already patches in the queue for:
- testing whether a variable is defined in psql
feature could eventually be added to pgbench as well
- adding \gset (& \cset) to pgbench to get output of possibly
combined queries into variables, which can be used for making
decisions later in the script.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-13.patchtext/x-diff; name=pgbench-more-ops-funcs-13.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index f5db8d1..59ca7a1 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -827,14 +827,31 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
      <para>
       Sets variable <replaceable>varname</> to a value calculated
       from <replaceable>expression</>.
-      The expression may contain integer constants such as <literal>5432</>,
+      The expression may contain the <literal>NULL</> constant,
+      boolean constants <literal>TRUE</> and <literal>FALSE</>,
+      integer constants such as <literal>5432</>,
       double constants such as <literal>3.14159</>,
       references to variables <literal>:</><replaceable>variablename</>,
-      unary operators (<literal>+</>, <literal>-</>) and binary operators
-      (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>,
-      <literal>%</>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</>,
+      SQL <link linkend="functions-case"><token>CASE</> generic conditional
+      expressions</> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</> on
+      <literal>NULL</> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are <literal>TRUE</>,
+      zero numerical values and <literal>NULL</> are <literal>FALSE</>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</> clause is provided to a <token>CASE</>,
+      the default value is <literal>NULL</>.
      </para>
 
      <para>
@@ -843,6 +860,7 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x <> 0 THEN :y/:x ELSE NULL END 
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -919,6 +937,177 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</>
+      <entry>Description</>
+      <entry>Example</>
+      <entry>Result</>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</></>
+      <entry>logical or</>
+      <entry><literal>5 or 0</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>AND</></>
+      <entry>logical and</>
+      <entry><literal>3 and 0</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>NOT</></>
+      <entry>logical not</>
+      <entry><literal>not false</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</></>
+      <entry>value tests</>
+      <entry><literal>1 is null</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</></>
+      <entry>null tests</>
+      <entry><literal>1 notnull</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>=</></>
+      <entry>is equal</>
+      <entry><literal>5 = 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</></>
+      <entry>is not equal</>
+      <entry><literal>5 &lt;&gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>!=</></>
+      <entry>is not equal</>
+      <entry><literal>5 != 5</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;</></>
+      <entry>lower than</>
+      <entry><literal>5 &lt; 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&lt;=</></>
+      <entry>lower or equal</>
+      <entry><literal>5 &lt;= 4</></>
+      <entry><literal>FALSE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;</></>
+      <entry>greater than</>
+      <entry><literal>5 &gt; 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>&gt;=</></>
+      <entry>greater or equal</>
+      <entry><literal>5 &gt;= 4</></>
+      <entry><literal>TRUE</></>
+     </row>
+     <row>
+      <entry><literal>|</></>
+      <entry>integer bitwise OR</>
+      <entry><literal>1 | 2</></>
+      <entry><literal>3</></>
+     </row>
+     <row>
+      <entry><literal>#</></>
+      <entry>integer bitwise XOR</>
+      <entry><literal>1 # 3</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>&amp;</></>
+      <entry>integer bitwise AND</>
+      <entry><literal>1 &amp 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>~</></>
+      <entry>integer bitwise NOT</>
+      <entry><literal>~ 1</></>
+      <entry><literal>-2</></>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</></>
+      <entry>integer bitwise shift left</>
+      <entry><literal>1 &lt;&lt; 2</></>
+      <entry><literal>4</></>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</></>
+      <entry>integer bitwise shift right</>
+      <entry><literal>8 &gt;&gt; 2</></>
+      <entry><literal>2</></>
+     </row>
+     <row>
+      <entry><literal>+</></>
+      <entry>addition</>
+      <entry><literal>5 + 4</></>
+      <entry><literal>9</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>substraction</>
+      <entry><literal>3 - 2.0</></>
+      <entry><literal>1.0</></>
+     </row>
+     <row>
+      <entry><literal>*</></>
+      <entry>multiplication</>
+      <entry><literal>5 * 4</></>
+      <entry><literal>20</></>
+     </row>
+     <row>
+      <entry><literal>/</></>
+      <entry>division (integer truncates the results)</>
+      <entry><literal>5 / 3</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>%</></>
+      <entry>modulo</>
+      <entry><literal>3 % 2</></>
+      <entry><literal>1</></>
+     </row>
+     <row>
+      <entry><literal>-</></>
+      <entry>opposite</>
+      <entry><literal>- 2.0</></>
+      <entry><literal>-2.0</></>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -965,6 +1154,13 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>5432.0</></>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>exponential</>
+       <entry><literal>exp(1.0)</></>
+       <entry><literal>2.718281828459045</></>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</> [, <replaceable>...</> ] )</></></>
        <entry>double if any <replaceable>a</> is double, else integer</>
        <entry>largest value among arguments</>
@@ -986,6 +1182,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</>
        <entry><literal>2.1</></>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</>)</></></>
+       <entry>double</>
+       <entry>natural logarithm</>
+       <entry><literal>ln(2.718281828459045)</></>
+       <entry><literal>1.0</></>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</>, <replaceable>j</>)</></></>
+       <entry>inteter</>
+       <entry>modulo</>
+       <entry><literal>mod(54, 32)</></>
+       <entry><literal>22</></>
+      </row>
+      <row>
        <entry><literal><function>pi()</></></>
        <entry>double</>
        <entry>value of the constant PI</>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9bf6d23..919bedb 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -69,6 +69,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -127,15 +143,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index e37496c..6d2dc90 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -447,6 +446,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -972,50 +973,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1092,7 +1125,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1118,10 +1151,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1129,7 +1162,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1138,11 +1171,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1155,7 +1188,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1254,6 +1287,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1263,11 +1351,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1276,6 +1363,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1287,12 +1379,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1322,13 +1434,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1337,6 +1453,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1346,6 +1470,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1381,6 +1509,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1409,6 +1553,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1449,6 +1609,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1487,13 +1704,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1503,6 +1723,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1513,6 +1735,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1591,6 +1818,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1664,6 +1908,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1700,10 +1954,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2256,7 +2510,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4175,16 +4429,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index fd428af..e1277a1 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 3609b9b..aa97b8c 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -198,10 +198,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=true -Df=off -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -210,46 +213,100 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=18.: int 9223372036854775807\b},
+	        # 19 has no output
+		qr{command=20.: boolean false\b},
+		qr{command=21.: boolean true\b},
+		qr{command=22.: int 22\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: double 24\b},
+		qr{command=25.: int 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: double 27\b},
+		qr{command=28.: int 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: boolean true\b},
+		qr{command=31.: null\b},
+		qr{command=32.: null\b},
+		qr{command=33.: boolean true\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: boolean true\b},
+		qr{command=38.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 22 else 0 end)
+\set c5 debug(case when true then 23 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 1.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 25 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 26 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.0 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 28 end)
+\set cb debug(mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 36 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -360,8 +417,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_exponential(0, 10, 0.0)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -383,7 +474,9 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -391,7 +484,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#71Pavel Stehule
pavel.stehule@gmail.com
In reply to: Fabien COELHO (#70)
Re: pgbench more operators & functions

Hi

2017-09-09 11:02 GMT+02:00 Fabien COELHO <coelho@cri.ensmp.fr>:

Hello Pavel,

Here is a v13. No code changes, but TAP tests added to maintain pgbench
coverage to green.

Summary of patch contents:

This patch extends pgbench expressions syntax while keeping compatibility
with SQL expressions.

It adds support for NULL and BOOLEAN, as well as assorted logical,
comparison and test operators (AND, <>, <=, IS NULL...).

A CASE construct is provided which takes advantage of the added BOOLEAN.

Integer and double functions and operators are also extended: bitwise
operators (<< & ...), exp/ln, mod() as synonymous to % (matching pg).

Added TAP tests maintain pgbench source coverage to green (if you ignore
lexer & parser generated files...).

Future plans include extending and synchronizing psql & pgbench variable
and expression syntaxes:
- move expression parsing and evaluation in fe_utils,
which would allow to
- extend psql with some \let i <expression> cliend-side syntax
(ISTM that extending the \set syntax cannot be upward compatible)
and probably handle \let as a synonymous to \set in pgbench.
- allow \if <expression> in psql instead of just \if <boolean>
- add \if ... support to pgbench
- maybe add TEXT type support to the expression engine, if useful
- maybe add :'var" and :"var" support to pgbench, if useful

There are already patches in the queue for:
- testing whether a variable is defined in psql
feature could eventually be added to pgbench as well
- adding \gset (& \cset) to pgbench to get output of possibly
combined queries into variables, which can be used for making
decisions later in the script.

--
Fabien.

1. there are no any problem with compilation, patching
2. all tests passed
3. doc is ok

I'll mark this patch as ready for commiter

Regards

Pavel

#72Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Pavel Stehule (#71)
Re: pgbench more operators & functions

Here is a v13. No code changes, but TAP tests added to maintain pgbench
coverage to green.

Summary of patch contents: [...]

1. there are no any problem with compilation, patching
2. all tests passed
3. doc is ok

I'll mark this patch as ready for commiter

Ok. Thanks for the reviews.

--
Fabien.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#72)
1 attachment(s)
Re: pgbench more operators & functions

Here is a v13. No code changes, but TAP tests added to maintain pgbench
coverage to green.

Here is a v14, which is just a rebase after the documentation xml-ization.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-14.patchtext/x-diff; name=pgbench-more-ops-funcs-14.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index e509e6c..1f55967 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -827,14 +827,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -843,6 +861,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -919,6 +938,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -965,6 +1155,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -986,6 +1183,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 5d8a01c..add653b 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -447,6 +446,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -972,50 +973,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1092,7 +1125,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1118,10 +1151,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1129,7 +1162,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1138,11 +1171,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1155,7 +1188,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1254,6 +1287,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1263,11 +1351,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1276,6 +1363,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1287,12 +1379,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1322,13 +1434,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1337,6 +1453,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1346,6 +1470,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1381,6 +1509,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1409,6 +1553,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1449,6 +1609,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1487,13 +1704,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1503,6 +1723,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1513,6 +1735,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1591,6 +1818,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1664,6 +1908,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1700,10 +1954,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2252,7 +2506,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4171,16 +4425,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index fd428af..e1277a1 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 11bc0fe..8e19bbd 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -198,10 +198,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=true -Df=off -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -210,46 +213,100 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=18.: int 9223372036854775807\b},
+	        # 19 has no output
+		qr{command=20.: boolean false\b},
+		qr{command=21.: boolean true\b},
+		qr{command=22.: int 22\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: double 24\b},
+		qr{command=25.: int 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: double 27\b},
+		qr{command=28.: int 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: boolean true\b},
+		qr{command=31.: null\b},
+		qr{command=32.: null\b},
+		qr{command=33.: boolean true\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: boolean true\b},
+		qr{command=38.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 22 else 0 end)
+\set c5 debug(case when true then 23 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 1.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 25 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 26 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.0 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 28 end)
+\set cb debug(mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 36 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -360,8 +417,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_exponential(0, 10, 0.0)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -383,7 +474,9 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -391,7 +484,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#74Pavel Stehule
pavel.stehule@gmail.com
In reply to: Fabien COELHO (#73)
Re: pgbench more operators & functions

Hi

2017-10-20 18:36 GMT+02:00 Fabien COELHO <coelho@cri.ensmp.fr>:

Here is a v13. No code changes, but TAP tests added to maintain pgbench

coverage to green.

Here is a v14, which is just a rebase after the documentation xml-ization.

all tests passed
no problems with doc building

Show quoted text

--
Fabien.

#75Michael Paquier
michael.paquier@gmail.com
In reply to: Pavel Stehule (#74)
Re: [HACKERS] pgbench more operators & functions

On Wed, Oct 25, 2017 at 5:57 PM, Pavel Stehule <pavel.stehule@gmail.com> wrote:

2017-10-20 18:36 GMT+02:00 Fabien COELHO <coelho@cri.ensmp.fr>:

Here is a v13. No code changes, but TAP tests added to maintain pgbench
coverage to green.

Here is a v14, which is just a rebase after the documentation xml-ization.

all tests passed
no problems with doc building

Moved to next CF. This is still parked as ready for committer.
--
Michael

#76Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#73)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Here is a v13. No code changes, but TAP tests added to maintain pgbench
coverage to green.

Here is a v14, which is just a rebase after the documentation xml-ization.

Regenerated v15 that applies cleanly on head. No changes.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-15.patchtext/x-diff; charset=us-ascii; name=pgbench-more-ops-funcs-15.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 94b495e..cac257b 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index bd96eae..ee6bc73 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -186,19 +186,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -454,6 +453,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -981,50 +982,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1101,7 +1134,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1127,10 +1160,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1138,7 +1171,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1147,11 +1180,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1164,7 +1197,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1263,6 +1296,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1272,11 +1360,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1285,6 +1372,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1296,12 +1388,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1331,13 +1443,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1346,6 +1462,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1355,6 +1479,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1390,6 +1518,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1418,6 +1562,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1458,6 +1618,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1496,13 +1713,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1512,6 +1732,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1522,6 +1744,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1600,6 +1827,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1673,6 +1917,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1709,10 +1963,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2284,7 +2538,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4376,16 +4630,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index fd428af..e1277a1 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index c095881..67214e3 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=true -Df=off -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,46 +226,100 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=18.: int 9223372036854775807\b},
+	        # 19 has no output
+		qr{command=20.: boolean false\b},
+		qr{command=21.: boolean true\b},
+		qr{command=22.: int 22\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: double 24\b},
+		qr{command=25.: int 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: double 27\b},
+		qr{command=28.: int 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: boolean true\b},
+		qr{command=31.: null\b},
+		qr{command=32.: null\b},
+		qr{command=33.: boolean true\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: boolean true\b},
+		qr{command=38.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 22 else 0 end)
+\set c5 debug(case when true then 23 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 1.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 25 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 26 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.0 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 28 end)
+\set cb debug(mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 36 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -373,8 +430,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_exponential(0, 10, 0.0)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -396,7 +487,9 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -404,7 +497,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#77Raúl Marín Rodríguez
rmrodriguez@carto.com
In reply to: Fabien COELHO (#76)
Re: [HACKERS] pgbench more operators & functions

Hi,

Regenerated v15 that applies cleanly on head. No changes.

I'm not sure why it wasn't failing before, but I have issues building the
doc:

+ <xref linkend="pgbench-operators"> are built into
<application>pgbench</application>
Missing '/' to close the xref

+ <entry><literal>1 &amp 3</literal></entry>
Expecting ';' as the previous use (&amp;)

On Fri, Dec 1, 2017 at 1:57 PM, Fabien COELHO <coelho@cri.ensmp.fr> wrote:

Here is a v13. No code changes, but TAP tests added to maintain pgbench

coverage to green.

Here is a v14, which is just a rebase after the documentation xml-ization.

Regenerated v15 that applies cleanly on head. No changes.

--
Fabien.

--

*Raúl Marín Rodríguez *carto.com

#78Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Raúl Marín Rodríguez (#77)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

I'm not sure why it wasn't failing before, but I have issues building the
doc:

+ <xref linkend="pgbench-operators"> are built into
<application>pgbench</application>
Missing '/' to close the xref

Indeed, missing xml-ization. The format was still SGML when the patch was
developed.

+ <entry><literal>1 &amp 3</literal></entry>
Expecting ';' as the previous use (&amp;)

Indeed, a typo.

Regenerated v15 that applies cleanly on head. No changes.

Attached v16 fixes those two errors. I regenerated the documentation with
the new xml toolchain, and made "check" overall and in pgbench.

Thanks for the debug,

--
Fabien.

Attachments:

pgbench-more-ops-funcs-16.patchtext/x-diff; name=pgbench-more-ops-funcs-16.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 94b495e..0708ad2 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index b3a2d9b..770be98 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -191,6 +292,48 @@ static const struct
 	{
 		"random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -279,6 +422,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -291,6 +442,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index bd96eae..ee6bc73 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -186,19 +186,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -454,6 +453,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -981,50 +982,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1101,7 +1134,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1127,10 +1160,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1138,7 +1171,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1147,11 +1180,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1164,7 +1197,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1263,6 +1296,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1272,11 +1360,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1285,6 +1372,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1296,12 +1388,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 
 /* assign an integer value */
@@ -1331,13 +1443,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1346,6 +1462,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1355,6 +1479,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1390,6 +1518,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1418,6 +1562,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1458,6 +1618,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1496,13 +1713,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1512,6 +1732,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1522,6 +1744,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1600,6 +1827,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1673,6 +1917,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1709,10 +1963,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2284,7 +2538,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4376,16 +4630,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index fd428af..e1277a1 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,9 +76,25 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
-	PGBENCH_RANDOM_EXPONENTIAL
+	PGBENCH_RANDOM_EXPONENTIAL,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index c095881..67214e3 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=true -Df=off -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,46 +226,100 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=18.: int 9223372036854775807\b},
+	        # 19 has no output
+		qr{command=20.: boolean false\b},
+		qr{command=21.: boolean true\b},
+		qr{command=22.: int 22\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: double 24\b},
+		qr{command=25.: int 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: double 27\b},
+		qr{command=28.: int 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: boolean true\b},
+		qr{command=31.: null\b},
+		qr{command=32.: null\b},
+		qr{command=33.: boolean true\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: boolean true\b},
+		qr{command=38.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 22 else 0 end)
+\set c5 debug(case when true then 23 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 1.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 25 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 26 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.0 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 28 end)
+\set cb debug(mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 36 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -373,8 +430,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_exponential(0, 10, 0.0)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -396,7 +487,9 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -404,7 +497,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#79Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#78)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Attached v16 fixes those two errors. I regenerated the documentation with the
new xml toolchain, and made "check" overall and in pgbench.

Attached v17 is a rebase after the zipfian commit.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-17.patchtext/x-diff; name=pgbench-more-ops-funcs-17.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 4431fc3..ea8f305 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 25d5ad4..14b3726 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -194,6 +295,48 @@ static const struct
 	{
 		"random_zipfian", 3, PGBENCH_RANDOM_ZIPFIAN
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -282,6 +425,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -294,6 +445,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 7ce6f60..39bfcc2 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,18 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.  If a variable only has a string value, "svalue" is that
+ * value, "has_value" is false, and "value" is undefined.  If the value is
+ * known, "has_value" is true and "value" contains the value (in any variant).
+ * In this case "svalue" contains the string equivalent of the value, if we've had
+ * occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	bool		has_value;		/* is actual value known? */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +487,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1147,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
 	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	Assert(var->has_value);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
 /* Try to convert variable to numeric form; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->has_value)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+		var->has_value = true;
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+		var->has_value = true;
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
+		var->has_value = true;
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
+		var->has_value = true;
 	}
 	return true;
 }
@@ -1266,7 +1299,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,10 +1325,10 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->has_value = false;
 
 	return true;
 }
@@ -1303,7 +1336,7 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 /* Assign a numeric value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1345,11 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->has_value = true;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1362,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1461,61 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+		return "<unknown>";
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1525,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1537,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1553,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1495,13 +1607,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1626,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1643,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1682,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1726,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1782,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1877,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1896,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +1908,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1764,6 +1991,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1850,6 +2094,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1886,10 +2140,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2461,7 +2715,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4564,16 +4818,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->has_value)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 83fee1a..6da60a8 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,10 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +47,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,10 +76,26 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
-	PGBENCH_RANDOM_ZIPFIAN
+	PGBENCH_RANDOM_ZIPFIAN,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index e3cdf28..9647885 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=true -Df=off -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,49 +226,102 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b}, ],
+		qr{command=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: boolean false\b},
+		qr{command=22.: boolean true\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: int 24\b},
+		qr{command=25.: double 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: int 27\b},
+		qr{command=28.: double 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: int 30\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: null\b},
+		qr{command=33.: null\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: boolean true\b},
+		qr{command=37.: int 37\b},
+		qr{command=38.: boolean true\b},
+		qr{command=39.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
 -- yet another integer function
 \set id debug(random_zipfian(1, 9, 1.3))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 23 else 0 end)
+\set c5 debug(case when true then 24 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 26 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 27 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 29 end)
+\set cb debug(1 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 37 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -384,8 +440,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -407,7 +497,9 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -415,7 +507,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#80Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#79)
Re: [HACKERS] pgbench more operators & functions

Huh, you are fast. Rebase patch during half an hour.

I haven't objection about patch idea, but I see some gotchas in coding.

1) /* Try to convert variable to numeric form; return false on failure */
static bool
makeVariableValue(Variable *var)

as now, makeVariableValue() convert value of eny type, not only numeric

2) In makeVariableValue():
if (pg_strcasecmp(var->svalue, "null") == 0)
...
else if (pg_strncasecmp(var->svalue, "true", slen)

mixing of pg_strcasecmp and pg_strNcasecmp. And, IMHO, result of
pg_strncasecmp("tru", "true", 1) will be 0. It may be good for 't' of 'f' but
it seems too free grammar to accept 'tr' or 'fa' or even 'o' which actually
not known to be on or off.

3) It seems to me that Variable.has_value could be eliminated and then new
PGBT_NOT_SET is added to PgBenchValueType enum as a first (=0) value. This
allows to shorten code and make it more readable, look
setBoolValue(&var->value, true);
var->has_value = true;
The second line actually doesn't needed. Although I don't insist to fix that.

I actually prefer PGBT_NOT_SET instead of kind of PGBT_UNDEFINED, because I
remember a collision in JavaScript with undefined and null variable.

4) valueTypeName()
it checks all possible PgBenchValue.type but believes that field could contain
some another value. In all other cases it treats as error or assert.

Fabien COELHO wrote:

Attached v16 fixes those two errors. I regenerated the documentation with the
new xml toolchain, and made "check" overall and in pgbench.

Attached v17 is a rebase after the zipfian commit.

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

#81Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#80)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Hello Teodor,

Huh, you are fast. Rebase patch during half an hour.

Hmmm... just lucky, and other after lunch tasks were more demanding.

I haven't objection about patch idea, but I see some gotchas in coding.

1) /* Try to convert variable to numeric form; return false on failure */
static bool
makeVariableValue(Variable *var)

as now, makeVariableValue() convert value of eny type, not only numeric

Indeed, the comments need updating. I found a few instances.

2) In makeVariableValue():
if (pg_strcasecmp(var->svalue, "null") == 0)
...
else if (pg_strncasecmp(var->svalue, "true", slen)

mixing of pg_strcasecmp and pg_strNcasecmp. And, IMHO, result of
pg_strncasecmp("tru", "true", 1) will be 0.

Yep, but it cannot be called like that because slen ==
strlen(var->svalue).

It may be good for 't' of 'f' but it seems too free grammar to accept
'tr' or 'fa' or even 'o' which actually not known to be on or off.

Yes, it really works like that. I tried to make something clearer than
"src/bin/psql/variable.c". Maybe I did not succeed.

I have added a comment and use some shortened versions in tests, with
the -D option.

3) It seems to me that Variable.has_value could be eliminated and then new
PGBT_NOT_SET is added to PgBenchValueType enum as a first (=0) value. This
allows to shorten code and make it more readable, look
setBoolValue(&var->value, true);
var->has_value = true;
The second line actually doesn't needed. Although I don't insist to fix that.

I agree that the redundancy should be avoided. I tried to keep
"is_numeric" under some form, but there is no added value doing that.

I actually prefer PGBT_NOT_SET instead of kind of PGBT_UNDEFINED, because I
remember a collision in JavaScript with undefined and null variable.

I used "PGBT_NO_VALUE" which seemed clearer otherwise a variable may be
set and its value would not "not set" which would look strange.

4) valueTypeName()
it checks all possible PgBenchValue.type but believes that field could
contain some another value. In all other cases it treats as error or assert.

Ok. Treated as an internal error with an assert.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-18.patchtext/x-diff; name=pgbench-more-ops-funcs-18.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 4431fc3..ea8f305 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 25d5ad4..14b3726 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,87 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(-1), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +185,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +205,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +214,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +250,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +272,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -194,6 +295,48 @@ static const struct
 	{
 		"random_zipfian", 3, PGBENCH_RANDOM_ZIPFIAN
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -282,6 +425,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -294,6 +445,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 7ce6f60..fc1199b 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,20 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.
+ *
+ * If a variable only has a string value, "svalue" is that value, and value is
+ * "not set".  If the value is known, "value" contains the value (in any
+ * variant).
+ *
+ * In this case "svalue" contains the string equivalent of the value, if we've
+ * had occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +489,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1149,78 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
-	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	/* We need to produce a string equivalent of the value */
+	Assert(var->value.type != PGBT_NO_VALUE);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
-/* Try to convert variable to numeric form; return false on failure */
+/* Try to convert variable to a value; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->value.type != PGBT_NO_VALUE)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+	}
+	/* accept prefixes such as y, ye, n, no... but not for "o" */
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
 	}
 	return true;
 }
@@ -1266,7 +1297,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,18 +1323,18 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->value.type = PGBT_NO_VALUE;
 
 	return true;
 }
 
-/* Assign a numeric value to a variable, creating it if need be */
+/* Assign a value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1343,10 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1359,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1458,67 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NO_VALUE)
+		return "none";
+	else if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+	{
+		/* internal error, should never get there */
+		Assert(false);
+		return NULL;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1528,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1540,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1556,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1495,13 +1610,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1629,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1646,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1685,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1729,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1785,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1880,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1899,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +1911,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1764,6 +1994,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1850,6 +2097,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1886,10 +2143,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2461,7 +2718,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4564,16 +4821,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->value.type != PGBT_NO_VALUE)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 83fee1a..bbca2be 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,11 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NO_VALUE,
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +48,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,10 +77,26 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
-	PGBENCH_RANDOM_ZIPFIAN
+	PGBENCH_RANDOM_ZIPFIAN,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index e3cdf28..968dc20 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=t -Df=of -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,49 +226,102 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b}, ],
+		qr{command=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: boolean false\b},
+		qr{command=22.: boolean true\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: int 24\b},
+		qr{command=25.: double 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: int 27\b},
+		qr{command=28.: double 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: int 30\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: null\b},
+		qr{command=33.: null\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: boolean true\b},
+		qr{command=37.: int 37\b},
+		qr{command=38.: boolean true\b},
+		qr{command=39.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
 -- yet another integer function
 \set id debug(random_zipfian(1, 9, 1.3))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 23 else 0 end)
+\set c5 debug(case when true then 24 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 26 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 27 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 29 end)
+\set cb debug(1 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 37 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -384,8 +440,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -407,7 +497,9 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -415,7 +507,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#82Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#81)
Re: [HACKERS] pgbench more operators & functions

2) In makeVariableValue():
if (pg_strcasecmp(var->svalue, "null") == 0)
...
else if (pg_strncasecmp(var->svalue, "true", slen)

mixing of pg_strcasecmp and pg_strNcasecmp. And, IMHO, result of
pg_strncasecmp("tru", "true", 1) willО©╫ be 0.

Yep, but it cannot be called like that because slen == strlen(var->svalue).

sorry, mistyped
pg_strncasecmp("tru", "true", 3) will be 0.

It may be good for 't' of 'f' but it seems too free grammar to accept 'tr' or
'fa' or even 'o' which actually not known to be on or off.

Yes, it really works like that. I tried to make something clearer than
"src/bin/psql/variable.c". Maybe I did not succeed.

Ok, I see. Current coding accepts truexxx, falsexxx, yesxx, noxxx but doesn't
accept offxxx and onxxx. Not so consistent as it could be. Also it doesn't
accept 1 and 0 as psql does, but it's obviously why.

I used "PGBT_NO_VALUE" which seemed clearer otherwise a variable may be set and
its value would not "not set" which would look strange.

Agree

Sorry, but I found more notices:
1) '~' and unary '-' should be commented, it's not so easy to guess about how
they actually implemented (-1 XOR value, remember that -1 is 0xfffff....)

2)
-   | expr '%' expr         { $$ = make_op(yyscanner, "%", $1, $3); }
+   | expr '%' expr         { $$ = make_op(yyscanner, "mod", $1, $3); }

why is MOD operation renamed? Do I miss something in thread?

Looking to psql and pgbench scripting implementation, isn't it better to
integrate lua in psql & pgbench?

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

#83Pavel Stehule
pavel.stehule@gmail.com
In reply to: Teodor Sigaev (#82)
Re: [HACKERS] pgbench more operators & functions

2017-12-15 14:47 GMT+01:00 Teodor Sigaev <teodor@sigaev.ru>:

2) In makeVariableValue():

if (pg_strcasecmp(var->svalue, "null") == 0)
...
else if (pg_strncasecmp(var->svalue, "true", slen)

mixing of pg_strcasecmp and pg_strNcasecmp. And, IMHO, result of
pg_strncasecmp("tru", "true", 1) will be 0.

Yep, but it cannot be called like that because slen ==
strlen(var->svalue).

sorry, mistyped
pg_strncasecmp("tru", "true", 3) will be 0.

It may be good for 't' of 'f' but it seems too free grammar to accept

'tr' or 'fa' or even 'o' which actually not known to be on or off.

Yes, it really works like that. I tried to make something clearer than
"src/bin/psql/variable.c". Maybe I did not succeed.

Ok, I see. Current coding accepts truexxx, falsexxx, yesxx, noxxx but
doesn't accept offxxx and onxxx. Not so consistent as it could be. Also it
doesn't accept 1 and 0 as psql does, but it's obviously why.

I used "PGBT_NO_VALUE" which seemed clearer otherwise a variable may be

set and its value would not "not set" which would look strange.

Agree

Sorry, but I found more notices:
1) '~' and unary '-' should be commented, it's not so easy to guess about
how they actually implemented (-1 XOR value, remember that -1 is
0xfffff....)

2)
-   | expr '%' expr         { $$ = make_op(yyscanner, "%", $1, $3); }
+   | expr '%' expr         { $$ = make_op(yyscanner, "mod", $1, $3); }

why is MOD operation renamed? Do I miss something in thread?

Looking to psql and pgbench scripting implementation, isn't it better to
integrate lua in psql & pgbench?

I don't think - I like Lua language, but it means no small new dependency.
These changes are only few lines and nobody expect building complex
applications in pgbench or psql.

Regards

Pavel

Show quoted text

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

#84Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#82)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Hello Teodor,

It may be good for 't' of 'f' but it seems too free grammar to accept 'tr'
or 'fa' or even 'o' which actually not known to be on or off.

Yes, it really works like that. I tried to make something clearer than
"src/bin/psql/variable.c". Maybe I did not succeed.

Ok, I see. Current coding accepts truexxx, falsexxx, yesxx, noxxx but doesn't
accept offxxx and onxxx. Not so consistent as it could be.

I've checked, but truexxx is not accepted as true. I have added a test
case which fails on "malformed variable", i.e. it went up to scanning a
double. When comparing ("truexxx", "true", 7) the fifth char is different,
so it is != 0. Or I'm missing something.

Also it doesn't accept 1 and 0 as psql does, but it's obviously why.

Yep. I have added a comment that it will be an int, and if a boolean is
needed it would work as expected.

Sorry, but I found more notices:
1) '~' and unary '-' should be commented, it's not so easy to guess about how
they actually implemented (-1 XOR value, remember that -1 is 0xfffff....)

Ok, I agree that it looks strange. I have added comments for both. I have
replaced -1 by 0xffff.... so that the code is hopefully clearer.

2)
-   | expr '%' expr         { $$ = make_op(yyscanner, "%", $1, $3); }
+   | expr '%' expr         { $$ = make_op(yyscanner, "mod", $1, $3); }

why is MOD operation renamed? Do I miss something in thread?

Because I have added MOD as an explicit function to match SQL, so now % is
just a shorthand for calling it. Before the patch there was only the '%'
operator. Changing the name is enough for the function to be found.

pg> SELECT 11 % 3, MOD(11, 3);
2 | 2

Looking to psql and pgbench scripting implementation, isn't it better to
integrate lua in psql & pgbench?

Hmmm... if it starts on this slope, everyone will have its opinion (lua,
tcl, python, ruby, perl, insert-script-name-here...) and it must interact
with SQL, I'm not sure how to embed SQL & another language cleanly. So the
idea is just to extend backslash command capabilities of psql & pgbench,
preferably consistently, when need (i.e. use cases) arises.

Attached a new version with these changes.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-19.patchtext/x-diff; name=pgbench-more-ops-funcs-19.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 4431fc3..ea8f305 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 25d5ad4..2de950f 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,23 +44,33 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
@@ -68,26 +82,89 @@ elist:                  	{ $$ = NULL; }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	/* unary minus "-x" implemented as "0 - x" */
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	/* binary ones complement "~x" implemented as 0xffff... xor x" */
+	| '~' expr 				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(0xffffffffffffffff), $2); }
+	| NOT_OP expr 			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
+	/* misc */
 	| VARIABLE 				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW              { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +187,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +207,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +216,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +252,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +274,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -194,6 +297,48 @@ static const struct
 	{
 		"random_zipfian", 3, PGBENCH_RANDOM_ZIPFIAN
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -282,6 +427,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -294,6 +447,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 7ce6f60..59f56d1 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,20 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.
+ *
+ * If a variable only has a string value, "svalue" is that value, and value is
+ * "not set".  If the value is known, "value" contains the value (in any
+ * variant).
+ *
+ * In this case "svalue" contains the string equivalent of the value, if we've
+ * had occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +489,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1149,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
-	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	/* We need to produce a string equivalent of the value */
+	Assert(var->value.type != PGBT_NO_VALUE);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
-/* Try to convert variable to numeric form; return false on failure */
+/* Try to convert variable to a value; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->value.type != PGBT_NO_VALUE)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+	}
+	/*
+	 * accept prefixes such as y, ye, n, no... but not for "o".
+	 * 0/1 are recognized later as an int, which is converted
+	 * to bool if needed.
+	 */
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
 	}
 	return true;
 }
@@ -1266,7 +1301,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,18 +1327,18 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->value.type = PGBT_NO_VALUE;
 
 	return true;
 }
 
-/* Assign a numeric value to a variable, creating it if need be */
+/* Assign a value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1347,10 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1363,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1462,67 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NO_VALUE)
+		return "none";
+	else if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+	{
+		/* internal error, should never get there */
+		Assert(false);
+		return NULL;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1532,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1544,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1560,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1495,13 +1614,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1633,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1650,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1689,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1733,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1789,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1884,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1903,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +1915,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1764,6 +1998,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1850,6 +2101,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1886,10 +2147,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2461,7 +2722,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4564,16 +4825,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->value.type != PGBT_NO_VALUE)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 83fee1a..bbca2be 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,11 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NO_VALUE,
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +48,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,10 +77,26 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
-	PGBENCH_RANDOM_ZIPFIAN
+	PGBENCH_RANDOM_ZIPFIAN,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index e3cdf28..be5bbfe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=t -Df=of -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,49 +226,102 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b}, ],
+		qr{command=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: boolean false\b},
+		qr{command=22.: boolean true\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: int 24\b},
+		qr{command=25.: double 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: int 27\b},
+		qr{command=28.: double 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: int 30\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: null\b},
+		qr{command=33.: null\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: boolean true\b},
+		qr{command=37.: int 37\b},
+		qr{command=38.: boolean true\b},
+		qr{command=39.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
 -- yet another integer function
 \set id debug(random_zipfian(1, 9, 1.3))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 23 else 0 end)
+\set c5 debug(case when true then 24 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 26 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 27 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 29 end)
+\set cb debug(1 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 37 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -384,8 +440,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -407,7 +497,10 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+	[ 'bad boolean', 0, [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -415,7 +508,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#85Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#84)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

I've checked, but truexxx is not accepted as true. I have added a test case
which fails on "malformed variable", i.e. it went up to scanning a double. When
comparing ("truexxx", "true", 7) the fifth char is different, so it is != 0. Or
I'm missing something.

Oh, my fault. I've missed that. Thank you for test

Ok, I agree that it looks strange. I have added comments for both. I have
replaced -1 by 0xffff.... so that the code is hopefully clearer.

I changed 0xff constant to ~INT64CONST(0), seems, it's more consistent way.
Also I remove some whitespaces in exprparse.y. Fixed version in attachment.

Looking to psql and pgbench scripting implementation, isn't it better to
integrate lua in psql & pgbench?

Hmmm... if it starts on this slope, everyone will have its opinion (lua, tcl,
python, ruby, perl, insert-script-name-here...) and it must interact with SQL,
I'm not sure how to embed SQL & another language cleanly. So the idea is just to
extend backslash command capabilities of psql & pgbench, preferably
consistently, when need (i.e. use cases) arises.

Actually, I prefer to see single scripting implementation in both psql and
pgbench, but I suppose nobody has a power to do it in foreseen future. And, may
be, it's not a very good way to invent one script language instead of using one
of bunch of them, but, again, I'm afraid several months/years discussion about
how and which one to embed. But scripting is needed now, I believe, at least I
see several test scenarios which can not be implemented with current pgbench and
this patch allows to do it.

So, I intend to push thish patch in current state. I saw several objections from
commiters in thread, but, seems, that objections are lifted. Am I right?

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

Attachments:

pgbench-more-ops-funcs-20.patchtext/x-patch; name=pgbench-more-ops-funcs-20.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 4431fc3eb7..ea8f305834 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1041,6 +1231,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>double(5432)</literal></entry>
        <entry><literal>5432.0</literal></entry>
       </row>
+      <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
       <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
@@ -1062,6 +1259,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>least(5, 4, 3, 2.1)</literal></entry>
        <entry><literal>2.1</literal></entry>
       </row>
+      <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
       <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 25d5ad48e5..08c7127145 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,53 +44,126 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
-
-/* Precedence: lowest to highest */
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
+
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
 result: expr				{ expr_parse_result = $1; }
 
-elist:                  	{ $$ = NULL; }
-	| expr 					{ $$ = make_elist($1, NULL); }
+elist:						{ $$ = NULL; }
+	| expr					{ $$ = make_elist($1, NULL); }
 	| elist ',' expr		{ $$ = make_elist($3, $1); }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	/* unary minus "-x" implemented as "0 - x" */
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	/* binary ones complement "~x" implemented as 0xffff... xor x" */
+	| '~' expr				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(~INT64CONST(0)), $2); }
+	| NOT_OP expr			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
-	| VARIABLE 				{ $$ = make_variable($1); }
+	/* misc */
+	| VARIABLE				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
+static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
 static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
@@ -109,6 +186,17 @@ make_double_constant(double dval)
 	return expr;
 }
 
+static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
 static PgBenchExpr *
 make_variable(char *varname)
 {
@@ -119,6 +207,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +216,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +252,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -176,6 +273,12 @@ static const struct
 	{
 		"sqrt", 1, PGBENCH_SQRT
 	},
+	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
 	{
 		"int", 1, PGBENCH_INT
 	},
@@ -194,6 +297,48 @@ static const struct
 	{
 		"random_zipfian", 3, PGBENCH_RANDOM_ZIPFIAN
 	},
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -282,6 +427,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -294,6 +447,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9db8..f4eb0d93de 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index 7ce6f607f5..59f56d1a2f 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,20 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.
+ *
+ * If a variable only has a string value, "svalue" is that value, and value is
+ * "not set".  If the value is known, "value" contains the value (in any
+ * variant).
+ *
+ * In this case "svalue" contains the string equivalent of the value, if we've
+ * had occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +489,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1149,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
-	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	/* We need to produce a string equivalent of the value */
+	Assert(var->value.type != PGBT_NO_VALUE);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
-/* Try to convert variable to numeric form; return false on failure */
+/* Try to convert variable to a value; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->value.type != PGBT_NO_VALUE)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
+	{
+		setNullValue(&var->value);
+	}
+	/*
+	 * accept prefixes such as y, ye, n, no... but not for "o".
+	 * 0/1 are recognized later as an int, which is converted
+	 * to bool if needed.
+	 */
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setBoolValue(&var->value, false);
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
 	}
 	return true;
 }
@@ -1266,7 +1301,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,18 +1327,18 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->value.type = PGBT_NO_VALUE;
 
 	return true;
 }
 
-/* Assign a numeric value to a variable, creating it if need be */
+/* Assign a value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1347,10 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1363,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1462,67 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NO_VALUE)
+		return "none";
+	else if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+	{
+		/* internal error, should never get there */
+		Assert(false);
+		return NULL;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1532,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1544,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1560,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1495,13 +1614,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1633,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1650,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1689,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1733,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1789,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1884,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1903,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +1915,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1764,6 +1998,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1850,6 +2101,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1886,10 +2147,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2461,7 +2722,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4564,16 +4825,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->value.type != PGBT_NO_VALUE)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 83fee1ae74..bbca2be543 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,11 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NO_VALUE,
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +48,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,10 +77,26 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
-	PGBENCH_RANDOM_ZIPFIAN
+	PGBENCH_RANDOM_ZIPFIAN,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index e3cdf28628..be5bbfe3c7 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=t -Df=of -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,49 +226,102 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b}, ],
+		qr{command=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: boolean false\b},
+		qr{command=22.: boolean true\b},
+		qr{command=23.: int 23\b},
+		qr{command=24.: int 24\b},
+		qr{command=25.: double 25\b},
+		qr{command=26.: int 26\b},
+		qr{command=27.: int 27\b},
+		qr{command=28.: double 28\b},
+		qr{command=29.: int 29\b},
+		qr{command=30.: int 30\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: null\b},
+		qr{command=33.: null\b},
+		qr{command=34.: boolean true\b},
+		qr{command=35.: boolean true\b},
+		qr{command=36.: boolean true\b},
+		qr{command=37.: int 37\b},
+		qr{command=38.: boolean true\b},
+		qr{command=39.: boolean true\b},
+	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
 \set i1 0
 -- yet another integer function
 \set id debug(random_zipfian(1, 9, 1.3))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 23 else 0 end)
+\set c5 debug(case when true then 24 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 4 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 26 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 27 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 9.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 29 end)
+\set cb debug(1 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 37 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -384,8 +440,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -407,7 +497,10 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+	[ 'bad boolean', 0, [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -415,7 +508,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#86Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#85)
Re: [HACKERS] pgbench more operators & functions

Hello Teodor,

replaced -1 by 0xffff.... so that the code is hopefully clearer.

I changed 0xff constant to ~INT64CONST(0), seems, it's more consistent way.
Also I remove some whitespaces in exprparse.y. Fixed version in attachment.

Fine, quite readable this way.

Actually, I prefer to see single scripting implementation in both psql and
pgbench,

I'll push for the implementations are to share more stuff in the future.

For instance the pgbench-if patch shares the conditional stack
implementation. I intend to move pgbench expression engine as a shared
front-end util, once its capabilites are extended and stable, which is
basically after this patch, so that client side expressions can be used in
psql.

Now, psql & pgbench contexts are slightly different, with an interactive
thing which must evaluate on the fly on one side and a scripting thing on
the other, so it would not be easy to share everything or to do everything
the same way.

but I suppose nobody has a power to do it in foreseen future. And,
may be, it's not a very good way to invent one script language instead of
using one of bunch of them, but, again, I'm afraid several months/years
discussion about how and which one to embed. But scripting is needed now, I
believe, at least I see several test scenarios which can not be implemented
with current pgbench and this patch allows to do it.

That is exactly why I'm pushing different things into pgbench (\gset,
\if, ...), to improve capabilities wrt to benchmarking.

So, I intend to push thish patch in current state. I saw several objections
from commiters in thread, but, seems, that objections are lifted. Am I right?

I think so.

--
Fabien.

#87Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#85)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Hello Teodor,

So, I intend to push thish patch in current state. I saw several objections
from commiters in thread, but, seems, that objections are lifted. Am I right?

Here is a rebase after "pow" addition.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-21.patchtext/x-diff; name=pgbench-more-ops-funcs-21.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1519fe7..3dd492c 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 74ffe5e..4104035 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,54 +44,127 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
 result: expr				{ expr_parse_result = $1; }
 
-elist:                  	{ $$ = NULL; }
-	| expr 					{ $$ = make_elist($1, NULL); }
+elist:						{ $$ = NULL; }
+	| expr					{ $$ = make_elist($1, NULL); }
 	| elist ',' expr		{ $$ = make_elist($3, $1); }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	/* unary minus "-x" implemented as "0 - x" */
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	/* binary ones complement "~x" implemented as 0xffff... xor x" */
+	| '~' expr				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(~INT64CONST(0)), $2); }
+	| NOT_OP expr			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
-	| VARIABLE 				{ $$ = make_variable($1); }
+	/* misc */
+	| VARIABLE				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +187,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +207,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +216,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +252,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +274,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -200,6 +303,52 @@ static const struct
 	{
 		"power", 2, PGBENCH_POW
 	},
+	/* logical operators */
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	/* bitwise integer operators */
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	/* comparison operators */
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	/* "case when ... then ... else ... end" construction */
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -288,6 +437,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -300,6 +457,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index e065f7b..14ed9f9 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,20 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.
+ *
+ * If a variable only has a string value, "svalue" is that value, and value is
+ * "not set".  If the value is known, "value" contains the value (in any
+ * variant).
+ *
+ * In this case "svalue" contains the string equivalent of the value, if we've
+ * had occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +489,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1149,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
-	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	/* We need to produce a string equivalent of the value */
+	Assert(var->value.type != PGBT_NO_VALUE);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
-/* Try to convert variable to numeric form; return false on failure */
+/* Try to convert variable to a value; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->value.type != PGBT_NO_VALUE)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+	}
+	/*
+	 * accept prefixes such as y, ye, n, no... but not for "o".
+	 * 0/1 are recognized later as an int, which is converted
+	 * to bool if needed.
+	 */
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
 	}
 	return true;
 }
@@ -1266,7 +1301,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,18 +1327,18 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->value.type = PGBT_NO_VALUE;
 
 	return true;
 }
 
-/* Assign a numeric value to a variable, creating it if need be */
+/* Assign a value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1347,10 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1363,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1462,67 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NO_VALUE)
+		return "none";
+	else if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+	{
+		/* internal error, should never get there */
+		Assert(false);
+		return NULL;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1532,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1544,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1560,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1495,13 +1614,17 @@ evalFunc(TState *thread, CState *st,
 		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1633,14 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null &&
+		func != PGBENCH_IS && func != PGBENCH_CASE && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1650,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1689,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1733,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1789,63 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_AND:
+		case PGBENCH_OR:
+			{
+				bool lb, rb;
+
+				if (!coerceToBool(&vargs[0], &lb) ||
+					!coerceToBool(&vargs[1], &rb))
+					return false;
+
+				if (func == PGBENCH_AND)
+					setBoolValue(retval, lb && rb);
+				else if (func == PGBENCH_OR)
+					setBoolValue(retval, lb || rb);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1884,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1903,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +1915,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1764,6 +1998,23 @@ evalFunc(TState *thread, CState *st,
 				}
 				return true;
 			}
+		case PGBENCH_CASE:
+			{
+				int		n_when = nargs / 2, i;
+				Assert(nargs >= 3 && nargs % 2 == 1);
+				/* return on first true when condition */
+				for (i = 0; i < n_when; i++)
+				{
+					if (valueTruth(&vargs[2*i]))
+					{
+						*retval = vargs[2*i+1];
+						return true;
+					}
+				}
+				/* else value is last */
+				*retval = vargs[nargs-1];
+				return true;
+			}
 
 			/* random functions */
 		case PGBENCH_RANDOM:
@@ -1868,6 +2119,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1904,10 +2165,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2479,7 +2740,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4582,16 +4843,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->value.type != PGBT_NO_VALUE)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 0e92882..05be954 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,11 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NO_VALUE,
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +48,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,11 +77,27 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
 	PGBENCH_RANDOM_ZIPFIAN,
-	PGBENCH_POW
+	PGBENCH_POW,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 9cbeb2f..8427f4a 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=t -Df=of -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,51 +226,61 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b},
-		qr{command=24.: double -27\b},
-		qr{command=25.: double 1024\b},
-		qr{command=26.: double 1\b},
-		qr{command=27.: double 1\b},
-		qr{command=28.: double -0.125\b},
-		qr{command=29.: double -0.125\b},
-		qr{command=30.: double -0.00032\b},
-		qr{command=31.: double 8.50705917302346e\+37\b},
-		qr{command=32.: double 1e\+30\b},
+		qr{command=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: double -27\b},
+		qr{command=22.: double 1024\b},
+		qr{command=23.: double 1\b},
+		qr{command=24.: double 1\b},
+		qr{command=25.: double -0.125\b},
+		qr{command=26.: double -0.125\b},
+		qr{command=27.: double -0.00032\b},
+		qr{command=28.: double 8.50705917302346e\+37\b},
+		qr{command=29.: double 1e\+30\b},
+		qr{command=30.: boolean false\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: int 32\b},
+		qr{command=33.: int 33\b},
+		qr{command=34.: double 34\b},
+		qr{command=35.: int 35\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: double 37\b},
+		qr{command=38.: int 38\b},
+		qr{command=39.: int 39\b},
+		qr{command=40.: boolean true\b},
+		qr{command=41.: null\b},
+		qr{command=42.: null\b},
+		qr{command=43.: boolean true\b},
+		qr{command=44.: boolean true\b},
+		qr{command=45.: boolean true\b},
+		qr{command=46.: int 46\b},
+		qr{command=47.: boolean true\b},
+		qr{command=48.: boolean true\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
@@ -284,8 +297,50 @@ pgbench(
 \set powernegd2 debug(power(-5.0,-5.0))
 \set powerov debug(pow(9223372036854775807, 2))
 \set powerov2 debug(pow(10,30))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 32 else 0 end)
+\set c5 debug(case when true then 33 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 13 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 35 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 36 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 12.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 38 end)
+\set cb debug(10 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 46 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -404,8 +459,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b 0.0 OR TRUE} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -427,7 +516,10 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+	[ 'bad boolean', 0, [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -435,7 +527,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#88Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#87)
Re: [HACKERS] pgbench more operators & functions

Here is a rebase after "pow" addition.

Huh, you are fast.

Investigating your patch I found that all arguments of logical AND/OR and CASE
are always evaluated. Seems, it should not be for pair of reasons:
- computing of unneeded args could be too expensive
- computing of unneeded args could complicate scripting code, look:
\set zy 0
\set yz case when :zy = 0 then -1 else (1 / :zy) end
This example will fail although programmer tried to check forbidden value

case when 1>0 then 1 when 1/0 > 0 then 0 else -1 end -- fails too

SQL doesn't evaluate unneeded arguments:
select case when 1>0 then 33 when 1/0 > 0 then -33 else null end;
case
------
33

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

#89Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#88)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Investigating your patch I found that all arguments of logical AND/OR and
CASE are always evaluated. Seems, it should not be for pair of reasons:

[...]

SQL doesn't evaluate unneeded arguments:

Here is a version with some lazy evaluation for and, or & case.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-22.patchtext/x-diff; name=pgbench-more-ops-funcs-22.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1519fe7..3dd492c 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 74ffe5e..4104035 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,54 +44,127 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
 result: expr				{ expr_parse_result = $1; }
 
-elist:                  	{ $$ = NULL; }
-	| expr 					{ $$ = make_elist($1, NULL); }
+elist:						{ $$ = NULL; }
+	| expr					{ $$ = make_elist($1, NULL); }
 	| elist ',' expr		{ $$ = make_elist($3, $1); }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	/* unary minus "-x" implemented as "0 - x" */
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	/* binary ones complement "~x" implemented as 0xffff... xor x" */
+	| '~' expr				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(~INT64CONST(0)), $2); }
+	| NOT_OP expr			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
-	| VARIABLE 				{ $$ = make_variable($1); }
+	/* misc */
+	| VARIABLE				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +187,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +207,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +216,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +252,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +274,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -200,6 +303,52 @@ static const struct
 	{
 		"power", 2, PGBENCH_POW
 	},
+	/* logical operators */
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	/* bitwise integer operators */
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	/* comparison operators */
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	/* "case when ... then ... else ... end" construction */
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -288,6 +437,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -300,6 +457,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index 9f46fb9..f4eb0d9 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index e065f7b..2f3f601 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,20 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.
+ *
+ * If a variable only has a string value, "svalue" is that value, and value is
+ * "not set".  If the value is known, "value" contains the value (in any
+ * variant).
+ *
+ * In this case "svalue" contains the string equivalent of the value, if we've
+ * had occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +489,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1149,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
-	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	/* We need to produce a string equivalent of the value */
+	Assert(var->value.type != PGBT_NO_VALUE);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
-/* Try to convert variable to numeric form; return false on failure */
+/* Try to convert variable to a value; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->value.type != PGBT_NO_VALUE)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+	}
+	/*
+	 * accept prefixes such as y, ye, n, no... but not for "o".
+	 * 0/1 are recognized later as an int, which is converted
+	 * to bool if needed.
+	 */
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
 	}
 	return true;
 }
@@ -1266,7 +1301,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,18 +1327,18 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->value.type = PGBT_NO_VALUE;
 
 	return true;
 }
 
-/* Assign a numeric value to a variable, creating it if need be */
+/* Assign a value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1347,10 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1363,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1462,67 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NO_VALUE)
+		return "none";
+	else if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+	{
+		/* internal error, should never get there */
+		Assert(false);
+		return NULL;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1532,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1544,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1560,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1484,24 +1603,137 @@ setDoubleValue(PgBenchValue *pv, double dval)
 	pv->u.dval = dval;
 }
 
+static bool isLazyFunc(PgBenchFunction func)
+{
+	return func == PGBENCH_AND || func == PGBENCH_OR || func == PGBENCH_CASE;
+}
+
+/* lazy evaluation of some functions */
+static bool
+evalLazyFunc(TState *thread, CState *st,
+			 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
+{
+	PgBenchValue a1, a2;
+	bool ba1, ba2;
+
+	Assert(isLazyFunc(func) && args != NULL && args->next != NULL);
+
+	if (!evaluateExpr(thread, st, args->expr, &a1))
+		return false;
+
+	switch (func)
+	{
+	case PGBENCH_AND:
+		if (a1.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+
+		if (!coerceToBool(&a1, &ba1))
+			return false;
+
+		if (!ba1)
+		{
+			setBoolValue(retval, false);
+			return true;
+		}
+
+		if (!evaluateExpr(thread, st, args->next->expr, &a2))
+			return false;
+
+		if (a2.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+		else if (!coerceToBool(&a2, &ba2))
+			return false;
+		else
+		{
+			setBoolValue(retval, ba2);
+			return true;
+		}
+
+		return true;
+
+	case PGBENCH_OR:
+
+		if (a1.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+
+		if (!coerceToBool(&a1, &ba1))
+			return false;
+
+		if (ba1)
+		{
+			setBoolValue(retval, true);
+			return true;
+		}
+
+		if (!evaluateExpr(thread, st, args->next->expr, &a2))
+			return false;
+
+		if (a2.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+		else if (!coerceToBool(&a2, &ba2))
+			return false;
+		else
+		{
+			setBoolValue(retval, ba2);
+			return true;
+		}
+
+	case PGBENCH_CASE:
+		/* when true */
+		if (valueTruth(&a1))
+			return evaluateExpr(thread, st, args->next->expr, retval);
+
+		/* final else case? */
+		if (args->next->next->next == NULL)
+			return evaluateExpr(thread, st, args->next->next->expr, retval);
+
+		/* no, another when, skip a value and proceed */
+		return evalLazyFunc(thread, st, PGBENCH_CASE, args->next->next, retval);
+
+	default:
+		/* internal error, cannot get here */
+		Assert(0);
+		break;
+	}
+	return false;
+}
+
 /* maximum number of function arguments */
 #define MAX_FARGS 16
 
 /*
- * Recursive evaluation of functions
+ * Recursive evaluation of standard functions,
+ * which do not require lazy evaluation.
  */
 static bool
-evalFunc(TState *thread, CState *st,
-		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
+evalStandardFunc(
+	TState *thread, CState *st,
+	PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1742,13 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null && func != PGBENCH_IS && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1758,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1797,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1841,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1897,45 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1974,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1993,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +2005,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1868,6 +2192,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1876,6 +2210,17 @@ evalFunc(TState *thread, CState *st,
 	}
 }
 
+/* evaluate some function */
+static bool
+evalFunc(TState *thread, CState *st,
+		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
+{
+	if (isLazyFunc(func))
+		return evalLazyFunc(thread, st, func, args, retval);
+	else
+		return evalStandardFunc(thread, st, func, args, retval);
+}
+
 /*
  * Recursive evaluation of an expression in a pgbench script
  * using the current state of variables.
@@ -1904,10 +2249,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2479,7 +2824,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4582,16 +4927,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->value.type != PGBT_NO_VALUE)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index 0e92882..05be954 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,11 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NO_VALUE,
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +48,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,11 +77,27 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
 	PGBENCH_RANDOM_ZIPFIAN,
-	PGBENCH_POW
+	PGBENCH_POW,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 9cbeb2f..4fb1308 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=t -Df=of -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,51 +226,61 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b},
-		qr{command=24.: double -27\b},
-		qr{command=25.: double 1024\b},
-		qr{command=26.: double 1\b},
-		qr{command=27.: double 1\b},
-		qr{command=28.: double -0.125\b},
-		qr{command=29.: double -0.125\b},
-		qr{command=30.: double -0.00032\b},
-		qr{command=31.: double 8.50705917302346e\+37\b},
-		qr{command=32.: double 1e\+30\b},
+		qr{command=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: double -27\b},
+		qr{command=22.: double 1024\b},
+		qr{command=23.: double 1\b},
+		qr{command=24.: double 1\b},
+		qr{command=25.: double -0.125\b},
+		qr{command=26.: double -0.125\b},
+		qr{command=27.: double -0.00032\b},
+		qr{command=28.: double 8.50705917302346e\+37\b},
+		qr{command=29.: double 1e\+30\b},
+		qr{command=30.: boolean false\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: int 32\b},
+		qr{command=33.: int 33\b},
+		qr{command=34.: double 34\b},
+		qr{command=35.: int 35\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: double 37\b},
+		qr{command=38.: int 38\b},
+		qr{command=39.: int 39\b},
+		qr{command=40.: boolean true\b},
+		qr{command=41.: null\b},
+		qr{command=42.: null\b},
+		qr{command=43.: boolean true\b},
+		qr{command=44.: boolean true\b},
+		qr{command=45.: boolean true\b},
+		qr{command=46.: int 46\b},
+		qr{command=47.: boolean true\b},
+		qr{command=48.: boolean true\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
@@ -284,8 +297,50 @@ pgbench(
 \set powernegd2 debug(power(-5.0,-5.0))
 \set powerov debug(pow(9223372036854775807, 2))
 \set powerov2 debug(pow(10,30))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 32 else 0 end)
+\set c5 debug(case when true then 33 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 13 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 35 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 36 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 12.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 38 end)
+\set cb debug(10 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 46 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -404,8 +459,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b NOT 0.0} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -427,7 +516,10 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+	[ 'bad boolean', 0, [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -435,7 +527,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#90Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#89)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

SQL doesn't evaluate unneeded arguments:

Here is a version with some lazy evaluation for and, or & case.

v23 is a rebase.

--
Fabien.

Attachments:

pgbench-more-ops-funcs-23.patchtext/x-diff; name=pgbench-more-ops-funcs-23.patchDownload
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml
index 1519fe7..3dd492c 100644
--- a/doc/src/sgml/ref/pgbench.sgml
+++ b/doc/src/sgml/ref/pgbench.sgml
@@ -904,14 +904,32 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
      <para>
       Sets variable <replaceable>varname</replaceable> to a value calculated
       from <replaceable>expression</replaceable>.
-      The expression may contain integer constants such as <literal>5432</literal>,
+      The expression may contain the <literal>NULL</literal> constant,
+      boolean constants <literal>TRUE</literal> and <literal>FALSE</literal>,
+      integer constants such as <literal>5432</literal>,
       double constants such as <literal>3.14159</literal>,
       references to variables <literal>:</literal><replaceable>variablename</replaceable>,
-      unary operators (<literal>+</literal>, <literal>-</literal>) and binary operators
-      (<literal>+</literal>, <literal>-</literal>, <literal>*</literal>, <literal>/</literal>,
-      <literal>%</literal>) with their usual precedence and associativity,
-      <link linkend="pgbench-builtin-functions">function calls</link>, and
-      parentheses.
+      <link linkend="pgbench-builtin-operators">operators</link>
+      with their usual SQL precedence and associativity,
+      <link linkend="pgbench-builtin-functions">function calls</link>,
+      SQL <link linkend="functions-case"><token>CASE</token> generic conditional
+      expressions</link> and parentheses.
+     </para>
+
+     <para>
+      Functions and most operators return <literal>NULL</literal> on
+      <literal>NULL</literal> input.
+     </para>
+
+     <para>
+      For conditional purposes, non zero numerical values are
+      <literal>TRUE</literal>, zero numerical values and <literal>NULL</literal>
+      are <literal>FALSE</literal>.
+     </para>
+
+     <para>
+      When no final <token>ELSE</token> clause is provided to a
+      <token>CASE</token>, the default value is <literal>NULL</literal>.
      </para>
 
      <para>
@@ -920,6 +938,7 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
 \set ntellers 10 * :scale
 \set aid (1021 * random(1, 100000 * :scale)) % \
            (100000 * :scale) + 1
+\set divx CASE WHEN :x &lt;&gt; 0 THEN :y/:x ELSE NULL END
 </programlisting></para>
     </listitem>
    </varlistentry>
@@ -996,6 +1015,177 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
   </variablelist>
  </refsect2>
 
+ <refsect2 id="pgbench-builtin-operators">
+  <title>Built-In Operators</title>
+
+  <para>
+   The arithmetic, bitwise, comparison and logical operators listed in
+   <xref linkend="pgbench-operators"/> are built into <application>pgbench</application>
+   and may be used in expressions appearing in
+   <link linkend="pgbench-metacommand-set"><literal>\set</literal></link>.
+  </para>
+
+  <table id="pgbench-operators">
+   <title>pgbench Operators by increasing precedence</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Operator</entry>
+      <entry>Description</entry>
+      <entry>Example</entry>
+      <entry>Result</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><literal>OR</literal></entry>
+      <entry>logical or</entry>
+      <entry><literal>5 or 0</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>AND</literal></entry>
+      <entry>logical and</entry>
+      <entry><literal>3 and 0</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>NOT</literal></entry>
+      <entry>logical not</entry>
+      <entry><literal>not false</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>IS [NOT] (NULL|TRUE|FALSE)</literal></entry>
+      <entry>value tests</entry>
+      <entry><literal>1 is null</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>ISNULL|NOTNULL</literal></entry>
+      <entry>null tests</entry>
+      <entry><literal>1 notnull</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>=</literal></entry>
+      <entry>is equal</entry>
+      <entry><literal>5 = 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&gt;</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 &lt;&gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>!=</literal></entry>
+      <entry>is not equal</entry>
+      <entry><literal>5 != 5</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;</literal></entry>
+      <entry>lower than</entry>
+      <entry><literal>5 &lt; 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;=</literal></entry>
+      <entry>lower or equal</entry>
+      <entry><literal>5 &lt;= 4</literal></entry>
+      <entry><literal>FALSE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;</literal></entry>
+      <entry>greater than</entry>
+      <entry><literal>5 &gt; 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;=</literal></entry>
+      <entry>greater or equal</entry>
+      <entry><literal>5 &gt;= 4</literal></entry>
+      <entry><literal>TRUE</literal></entry>
+     </row>
+     <row>
+      <entry><literal>|</literal></entry>
+      <entry>integer bitwise OR</entry>
+      <entry><literal>1 | 2</literal></entry>
+      <entry><literal>3</literal></entry>
+     </row>
+     <row>
+      <entry><literal>#</literal></entry>
+      <entry>integer bitwise XOR</entry>
+      <entry><literal>1 # 3</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&amp;</literal></entry>
+      <entry>integer bitwise AND</entry>
+      <entry><literal>1 &amp; 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>~</literal></entry>
+      <entry>integer bitwise NOT</entry>
+      <entry><literal>~ 1</literal></entry>
+      <entry><literal>-2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;</literal></entry>
+      <entry>integer bitwise shift left</entry>
+      <entry><literal>1 &lt;&lt; 2</literal></entry>
+      <entry><literal>4</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&gt;&gt;</literal></entry>
+      <entry>integer bitwise shift right</entry>
+      <entry><literal>8 &gt;&gt; 2</literal></entry>
+      <entry><literal>2</literal></entry>
+     </row>
+     <row>
+      <entry><literal>+</literal></entry>
+      <entry>addition</entry>
+      <entry><literal>5 + 4</literal></entry>
+      <entry><literal>9</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>substraction</entry>
+      <entry><literal>3 - 2.0</literal></entry>
+      <entry><literal>1.0</literal></entry>
+     </row>
+     <row>
+      <entry><literal>*</literal></entry>
+      <entry>multiplication</entry>
+      <entry><literal>5 * 4</literal></entry>
+      <entry><literal>20</literal></entry>
+     </row>
+     <row>
+      <entry><literal>/</literal></entry>
+      <entry>division (integer truncates the results)</entry>
+      <entry><literal>5 / 3</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>%</literal></entry>
+      <entry>modulo</entry>
+      <entry><literal>3 % 2</literal></entry>
+      <entry><literal>1</literal></entry>
+     </row>
+     <row>
+      <entry><literal>-</literal></entry>
+      <entry>opposite</entry>
+      <entry><literal>- 2.0</literal></entry>
+      <entry><literal>-2.0</literal></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </refsect2>
+
  <refsect2 id="pgbench-builtin-functions">
   <title>Built-In Functions</title>
 
@@ -1042,6 +1232,13 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>5432.0</literal></entry>
       </row>
       <row>
+       <entry><literal><function>exp(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>exponential</entry>
+       <entry><literal>exp(1.0)</literal></entry>
+       <entry><literal>2.718281828459045</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>greatest(<replaceable>a</replaceable> [, <replaceable>...</replaceable> ] )</function></literal></entry>
        <entry>double if any <replaceable>a</replaceable> is double, else integer</entry>
        <entry>largest value among arguments</entry>
@@ -1063,6 +1260,20 @@ pgbench <optional> <replaceable>options</replaceable> </optional> <replaceable>d
        <entry><literal>2.1</literal></entry>
       </row>
       <row>
+       <entry><literal><function>ln(<replaceable>x</replaceable>)</function></literal></entry>
+       <entry>double</entry>
+       <entry>natural logarithm</entry>
+       <entry><literal>ln(2.718281828459045)</literal></entry>
+       <entry><literal>1.0</literal></entry>
+      </row>
+      <row>
+       <entry><literal><function>mod(<replaceable>i</replaceable>, <replaceable>bj</replaceable>)</function></literal></entry>
+       <entry>integer</entry>
+       <entry>modulo</entry>
+       <entry><literal>mod(54, 32)</literal></entry>
+       <entry><literal>22</literal></entry>
+      </row>
+      <row>
        <entry><literal><function>pi()</function></literal></entry>
        <entry>double</entry>
        <entry>value of the constant PI</entry>
diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y
index 26494fd..b6e0b88 100644
--- a/src/bin/pgbench/exprparse.y
+++ b/src/bin/pgbench/exprparse.y
@@ -19,13 +19,17 @@
 PgBenchExpr *expr_parse_result;
 
 static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list);
+static PgBenchExpr *make_null_constant(void);
+static PgBenchExpr *make_boolean_constant(bool bval);
 static PgBenchExpr *make_integer_constant(int64 ival);
 static PgBenchExpr *make_double_constant(double dval);
 static PgBenchExpr *make_variable(char *varname);
 static PgBenchExpr *make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr);
+static PgBenchExpr *make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr);
 static int	find_func(yyscan_t yyscanner, const char *fname);
 static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args);
+static PgBenchExpr *make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part);
 
 %}
 
@@ -40,54 +44,127 @@ static PgBenchExpr *make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *
 {
 	int64		ival;
 	double		dval;
+	bool		bval;
 	char	   *str;
 	PgBenchExpr *expr;
 	PgBenchExprList *elist;
 }
 
-%type <elist> elist
-%type <expr> expr
+%type <elist> elist when_then_list
+%type <expr> expr case_control
 %type <ival> INTEGER_CONST function
 %type <dval> DOUBLE_CONST
+%type <bval> BOOLEAN_CONST
 %type <str> VARIABLE FUNCTION
 
-%token INTEGER_CONST DOUBLE_CONST VARIABLE FUNCTION
+%token NULL_CONST INTEGER_CONST DOUBLE_CONST BOOLEAN_CONST VARIABLE FUNCTION
+%token AND_OP OR_OP NOT_OP NE_OP LE_OP GE_OP LS_OP RS_OP IS_OP
+%token CASE_KW WHEN_KW THEN_KW ELSE_KW END_KW
 
-/* Precedence: lowest to highest */
+/* Precedence: lowest to highest, taken from postgres SQL parser */
+%left	OR_OP
+%left	AND_OP
+%right  NOT_OP
+%nonassoc IS_OP ISNULL_OP NOTNULL_OP
+%nonassoc '<' '>' '=' LE_OP GE_OP NE_OP
+%left   '|' '#' '&' LS_OP RS_OP '~'
 %left	'+' '-'
 %left	'*' '/' '%'
-%right	UMINUS
+%right	UNARY
 
 %%
 
 result: expr				{ expr_parse_result = $1; }
 
-elist:                  	{ $$ = NULL; }
-	| expr 					{ $$ = make_elist($1, NULL); }
+elist:						{ $$ = NULL; }
+	| expr					{ $$ = make_elist($1, NULL); }
 	| elist ',' expr		{ $$ = make_elist($3, $1); }
 	;
 
 expr: '(' expr ')'			{ $$ = $2; }
-	| '+' expr %prec UMINUS	{ $$ = $2; }
-	| '-' expr %prec UMINUS	{ $$ = make_op(yyscanner, "-",
+	| '+' expr %prec UNARY	{ $$ = $2; }
+	/* unary minus "-x" implemented as "0 - x" */
+	| '-' expr %prec UNARY	{ $$ = make_op(yyscanner, "-",
 										   make_integer_constant(0), $2); }
+	/* binary ones complement "~x" implemented as 0xffff... xor x" */
+	| '~' expr				{ $$ = make_op(yyscanner, "#",
+										   make_integer_constant(~INT64CONST(0)), $2); }
+	| NOT_OP expr			{ $$ = make_uop(yyscanner, "!not", $2); }
 	| expr '+' expr			{ $$ = make_op(yyscanner, "+", $1, $3); }
 	| expr '-' expr			{ $$ = make_op(yyscanner, "-", $1, $3); }
 	| expr '*' expr			{ $$ = make_op(yyscanner, "*", $1, $3); }
 	| expr '/' expr			{ $$ = make_op(yyscanner, "/", $1, $3); }
-	| expr '%' expr			{ $$ = make_op(yyscanner, "%", $1, $3); }
+	| expr '%' expr			{ $$ = make_op(yyscanner, "mod", $1, $3); }
+	| expr '<' expr			{ $$ = make_op(yyscanner, "<", $1, $3); }
+	| expr LE_OP expr		{ $$ = make_op(yyscanner, "<=", $1, $3); }
+	| expr '>' expr			{ $$ = make_op(yyscanner, "<", $3, $1); }
+	| expr GE_OP expr		{ $$ = make_op(yyscanner, "<=", $3, $1); }
+	| expr '=' expr			{ $$ = make_op(yyscanner, "=", $1, $3); }
+	| expr NE_OP expr		{ $$ = make_op(yyscanner, "<>", $1, $3); }
+	| expr '&' expr			{ $$ = make_op(yyscanner, "&", $1, $3); }
+	| expr '|' expr			{ $$ = make_op(yyscanner, "|", $1, $3); }
+	| expr '#' expr			{ $$ = make_op(yyscanner, "#", $1, $3); }
+	| expr LS_OP expr		{ $$ = make_op(yyscanner, "<<", $1, $3); }
+	| expr RS_OP expr		{ $$ = make_op(yyscanner, ">>", $1, $3); }
+	| expr AND_OP expr		{ $$ = make_op(yyscanner, "!and", $1, $3); }
+	| expr OR_OP expr		{ $$ = make_op(yyscanner, "!or", $1, $3); }
+	/* IS variants */
+	| expr ISNULL_OP		{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr NOTNULL_OP		{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP NULL_CONST	{ $$ = make_op(yyscanner, "!is", $1, make_null_constant()); }
+	| expr IS_OP NOT_OP NULL_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_null_constant()));
+							}
+	| expr IS_OP BOOLEAN_CONST
+							{
+								$$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3));
+							}
+	| expr IS_OP NOT_OP BOOLEAN_CONST
+							{
+								$$ = make_uop(yyscanner, "!not",
+											  make_op(yyscanner, "!is", $1, make_boolean_constant($4)));
+							}
+	/* constants */
+	| NULL_CONST			{ $$ = make_null_constant(); }
+	| BOOLEAN_CONST			{ $$ = make_boolean_constant($1); }
 	| INTEGER_CONST			{ $$ = make_integer_constant($1); }
 	| DOUBLE_CONST			{ $$ = make_double_constant($1); }
-	| VARIABLE 				{ $$ = make_variable($1); }
+	/* misc */
+	| VARIABLE				{ $$ = make_variable($1); }
 	| function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); }
+	| case_control			{ $$ = $1; }
 	;
 
+when_then_list:
+	  when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); }
+	| WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); }
+
+case_control:
+	  CASE_KW when_then_list END_KW { $$ = make_case(yyscanner, $2, make_null_constant()); }
+	| CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); }
+
 function: FUNCTION			{ $$ = find_func(yyscanner, $1); pg_free($1); }
 	;
 
 %%
 
 static PgBenchExpr *
+make_null_constant(void)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_NULL;
+	expr->u.constant.u.ival = 0;
+	return expr;
+}
+
+static PgBenchExpr *
 make_integer_constant(int64 ival)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -110,6 +187,17 @@ make_double_constant(double dval)
 }
 
 static PgBenchExpr *
+make_boolean_constant(bool bval)
+{
+	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
+
+	expr->etype = ENODE_CONSTANT;
+	expr->u.constant.type = PGBT_BOOLEAN;
+	expr->u.constant.u.bval = bval;
+	return expr;
+}
+
+static PgBenchExpr *
 make_variable(char *varname)
 {
 	PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr));
@@ -119,6 +207,7 @@ make_variable(char *varname)
 	return expr;
 }
 
+/* binary operators */
 static PgBenchExpr *
 make_op(yyscan_t yyscanner, const char *operator,
 		PgBenchExpr *lexpr, PgBenchExpr *rexpr)
@@ -127,11 +216,19 @@ make_op(yyscan_t yyscanner, const char *operator,
 					 make_elist(rexpr, make_elist(lexpr, NULL)));
 }
 
+/* unary operator */
+static PgBenchExpr *
+make_uop(yyscan_t yyscanner, const char *operator, PgBenchExpr *expr)
+{
+	return make_func(yyscanner, find_func(yyscanner, operator), make_elist(expr, NULL));
+}
+
 /*
  * List of available functions:
- * - fname: function name
+ * - fname: function name, "!..." for special internal functions
  * - nargs: number of arguments
  *			-1 is a special value for least & greatest meaning #args >= 1
+ *			-2 is for the "CASE WHEN ..." function, which has #args >= 3 and odd
  * - tag: function identifier from PgBenchFunction enum
  */
 static const struct
@@ -155,7 +252,7 @@ static const struct
 		"/", 2, PGBENCH_DIV
 	},
 	{
-		"%", 2, PGBENCH_MOD
+		"mod", 2, PGBENCH_MOD
 	},
 	/* actual functions */
 	{
@@ -177,6 +274,12 @@ static const struct
 		"sqrt", 1, PGBENCH_SQRT
 	},
 	{
+		"ln", 1, PGBENCH_LN
+	},
+	{
+		"exp", 1, PGBENCH_EXP
+	},
+	{
 		"int", 1, PGBENCH_INT
 	},
 	{
@@ -200,6 +303,52 @@ static const struct
 	{
 		"power", 2, PGBENCH_POW
 	},
+	/* logical operators */
+	{
+		"!and", 2, PGBENCH_AND
+	},
+	{
+		"!or", 2, PGBENCH_OR
+	},
+	{
+		"!not", 1, PGBENCH_NOT
+	},
+	/* bitwise integer operators */
+	{
+		"&", 2, PGBENCH_BITAND
+	},
+	{
+		"|", 2, PGBENCH_BITOR
+	},
+	{
+		"#", 2, PGBENCH_BITXOR
+	},
+	{
+		"<<", 2, PGBENCH_LSHIFT
+	},
+	{
+		">>", 2, PGBENCH_RSHIFT
+	},
+	/* comparison operators */
+	{
+		"=", 2, PGBENCH_EQ
+	},
+	{
+		"<>", 2, PGBENCH_NE
+	},
+	{
+		"<=", 2, PGBENCH_LE
+	},
+	{
+		"<", 2, PGBENCH_LT
+	},
+	{
+		"!is", 2, PGBENCH_IS
+	},
+	/* "case when ... then ... else ... end" construction */
+	{
+		"!case_end", -2, PGBENCH_CASE
+	},
 	/* keep as last array element */
 	{
 		NULL, 0, 0
@@ -288,6 +437,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 		elist_length(args) == 0)
 		expr_yyerror_more(yyscanner, "at least one argument expected",
 						  PGBENCH_FUNCTIONS[fnumber].fname);
+	/* special case: case (when ... then ...)+ (else ...)? end */
+	if (PGBENCH_FUNCTIONS[fnumber].nargs == -2)
+	{
+		int len = elist_length(args);
+		if (len < 3 || len % 2 != 1)
+			expr_yyerror_more(yyscanner, "odd and >= 3 number of arguments expected",
+							  "case control structure");
+	}
 
 	expr->etype = ENODE_FUNCTION;
 	expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag;
@@ -300,6 +457,14 @@ make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args)
 	return expr;
 }
 
+static PgBenchExpr *
+make_case(yyscan_t yyscanner, PgBenchExprList *when_then_list, PgBenchExpr *else_part)
+{
+	return make_func(yyscanner,
+					 find_func(yyscanner, "!case_end"),
+					 make_elist(else_part, when_then_list));
+}
+
 /*
  * exprscan.l is compiled as part of exprparse.y.  Currently, this is
  * unavoidable because exprparse does not create a .h file to export
diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l
index b86e77a..5c1bd88 100644
--- a/src/bin/pgbench/exprscan.l
+++ b/src/bin/pgbench/exprscan.l
@@ -71,6 +71,22 @@ newline			[\n]
 /* Line continuation marker */
 continuation	\\{newline}
 
+/* case insensitive keywords */
+and				[Aa][Nn][Dd]
+or				[Oo][Rr]
+not				[Nn][Oo][Tt]
+case			[Cc][Aa][Ss][Ee]
+when			[Ww][Hh][Ee][Nn]
+then			[Tt][Hh][Ee][Nn]
+else			[Ee][Ll][Ss][Ee]
+end				[Ee][Nn][Dd]
+true			[Tt][Rr][Uu][Ee]
+false			[Ff][Aa][Ll][Ss][Ee]
+null			[Nn][Uu][Ll][Ll]
+is				[Ii][Ss]
+isnull			[Ii][Ss][Nn][Uu][Ll][Ll]
+notnull			[Nn][Oo][Tt][Nn][Uu][Ll][Ll]
+
 /* Exclusive states */
 %x EXPR
 
@@ -129,15 +145,52 @@ continuation	\\{newline}
 "-"				{ return '-'; }
 "*"				{ return '*'; }
 "/"				{ return '/'; }
-"%"				{ return '%'; }
+"%"				{ return '%'; } /* C version, also in Pg SQL */
+"="				{ return '='; }
+"<>"			{ return NE_OP; }
+"!="			{ return NE_OP; } /* C version, also in Pg SQL */
+"<="			{ return LE_OP; }
+">="			{ return GE_OP; }
+"<<"			{ return LS_OP; }
+">>"			{ return RS_OP; }
+"<"				{ return '<'; }
+">"				{ return '>'; }
+"|"				{ return '|'; }
+"&"				{ return '&'; }
+"#"				{ return '#'; }
+"~"				{ return '~'; }
+
 "("				{ return '('; }
 ")"				{ return ')'; }
 ","				{ return ','; }
 
+{and}			{ return AND_OP; }
+{or}			{ return OR_OP; }
+{not}			{ return NOT_OP; }
+{is}			{ return IS_OP; }
+{isnull}		{ return ISNULL_OP; }
+{notnull}		{ return NOTNULL_OP; }
+
+{case}			{ return CASE_KW; }
+{when}			{ return WHEN_KW; }
+{then}			{ return THEN_KW; }
+{else}			{ return ELSE_KW; }
+{end}			{ return END_KW; }
+
 :{alnum}+		{
 					yylval->str = pg_strdup(yytext + 1);
 					return VARIABLE;
 				}
+
+{null}			{ return NULL_CONST; }
+{true}			{
+					yylval->bval = true;
+					return BOOLEAN_CONST;
+				}
+{false}			{
+					yylval->bval = false;
+					return BOOLEAN_CONST;
+				}
 {digit}+		{
 					yylval->ival = strtoint64(yytext);
 					return INTEGER_CONST;
diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c
index fc2c734..0e21119 100644
--- a/src/bin/pgbench/pgbench.c
+++ b/src/bin/pgbench/pgbench.c
@@ -189,19 +189,20 @@ const char *progname;
 volatile bool timer_exceeded = false;	/* flag from signal handler */
 
 /*
- * Variable definitions.  If a variable has a string value, "value" is that
- * value, is_numeric is false, and num_value is undefined.  If the value is
- * known to be numeric, is_numeric is true and num_value contains the value
- * (in any permitted numeric variant).  In this case "value" contains the
- * string equivalent of the number, if we've had occasion to compute that,
- * or NULL if we haven't.
+ * Variable definitions.
+ *
+ * If a variable only has a string value, "svalue" is that value, and value is
+ * "not set".  If the value is known, "value" contains the value (in any
+ * variant).
+ *
+ * In this case "svalue" contains the string equivalent of the value, if we've
+ * had occasion to compute that, or NULL if we haven't.
  */
 typedef struct
 {
 	char	   *name;			/* variable's name */
-	char	   *value;			/* its value in string form, if known */
-	bool		is_numeric;		/* is numeric value known? */
-	PgBenchValue num_value;		/* variable's value in numeric form */
+	char	   *svalue;			/* its value in string form, if known */
+	PgBenchValue value;			/* actual variable's value */
 } Variable;
 
 #define MAX_SCRIPTS		128		/* max number of SQL scripts allowed */
@@ -488,6 +489,8 @@ static const BuiltinScript builtin_script[] =
 
 
 /* Function prototypes */
+static void setNullValue(PgBenchValue *pv);
+static void setBoolValue(PgBenchValue *pv, bool bval);
 static void setIntValue(PgBenchValue *pv, int64 ival);
 static void setDoubleValue(PgBenchValue *pv, double dval);
 static bool evaluateExpr(TState *, CState *, PgBenchExpr *, PgBenchValue *);
@@ -1146,50 +1149,82 @@ getVariable(CState *st, char *name)
 	if (var == NULL)
 		return NULL;			/* not found */
 
-	if (var->value)
-		return var->value;		/* we have it in string form */
+	if (var->svalue)
+		return var->svalue;		/* we have it in string form */
 
-	/* We need to produce a string equivalent of the numeric value */
-	Assert(var->is_numeric);
-	if (var->num_value.type == PGBT_INT)
+	/* We need to produce a string equivalent of the value */
+	Assert(var->value.type != PGBT_NO_VALUE);
+	if (var->value.type == PGBT_NULL)
+		snprintf(stringform, sizeof(stringform), "NULL");
+	else if (var->value.type == PGBT_BOOLEAN)
 		snprintf(stringform, sizeof(stringform),
-				 INT64_FORMAT, var->num_value.u.ival);
-	else
-	{
-		Assert(var->num_value.type == PGBT_DOUBLE);
+				 "%s", var->value.u.bval ? "true" : "false");
+	else if (var->value.type == PGBT_INT)
 		snprintf(stringform, sizeof(stringform),
-				 "%.*g", DBL_DIG, var->num_value.u.dval);
-	}
-	var->value = pg_strdup(stringform);
-	return var->value;
+				 INT64_FORMAT, var->value.u.ival);
+	else if (var->value.type == PGBT_DOUBLE)
+		snprintf(stringform, sizeof(stringform),
+				 "%.*g", DBL_DIG, var->value.u.dval);
+	else /* internal error, unexpected type */
+		Assert(0);
+	var->svalue = pg_strdup(stringform);
+	return var->svalue;
 }
 
-/* Try to convert variable to numeric form; return false on failure */
+/* Try to convert variable to a value; return false on failure */
 static bool
-makeVariableNumeric(Variable *var)
+makeVariableValue(Variable *var)
 {
-	if (var->is_numeric)
+	size_t slen;
+
+	if (var->value.type != PGBT_NO_VALUE)
 		return true;			/* no work */
 
-	if (is_an_int(var->value))
+	slen = strlen(var->svalue);
+
+	if (slen == 0)
+		/* what should it do on ""? */
+		return false;
+
+	if (pg_strcasecmp(var->svalue, "null") == 0)
 	{
-		setIntValue(&var->num_value, strtoint64(var->value));
-		var->is_numeric = true;
+		setNullValue(&var->value);
+	}
+	/*
+	 * accept prefixes such as y, ye, n, no... but not for "o".
+	 * 0/1 are recognized later as an int, which is converted
+	 * to bool if needed.
+	 */
+	else if (pg_strncasecmp(var->svalue, "true", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "yes", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "on") == 0)
+	{
+		setBoolValue(&var->value, true);
+	}
+	else if (pg_strncasecmp(var->svalue, "false", slen) == 0 ||
+			 pg_strncasecmp(var->svalue, "no", slen) == 0 ||
+			 pg_strcasecmp(var->svalue, "off") == 0 ||
+			 pg_strcasecmp(var->svalue, "of") == 0)
+	{
+		setBoolValue(&var->value, false);
+	}
+	else if (is_an_int(var->svalue))
+	{
+		setIntValue(&var->value, strtoint64(var->svalue));
 	}
 	else						/* type should be double */
 	{
 		double		dv;
 		char		xs;
 
-		if (sscanf(var->value, "%lf%c", &dv, &xs) != 1)
+		if (sscanf(var->svalue, "%lf%c", &dv, &xs) != 1)
 		{
 			fprintf(stderr,
 					"malformed variable \"%s\" value: \"%s\"\n",
-					var->name, var->value);
+					var->name, var->svalue);
 			return false;
 		}
-		setDoubleValue(&var->num_value, dv);
-		var->is_numeric = true;
+		setDoubleValue(&var->value, dv);
 	}
 	return true;
 }
@@ -1266,7 +1301,7 @@ lookupCreateVariable(CState *st, const char *context, char *name)
 		var = &newvars[st->nvariables];
 
 		var->name = pg_strdup(name);
-		var->value = NULL;
+		var->svalue = NULL;
 		/* caller is expected to initialize remaining fields */
 
 		st->nvariables++;
@@ -1292,18 +1327,18 @@ putVariable(CState *st, const char *context, char *name, const char *value)
 	/* dup then free, in case value is pointing at this variable */
 	val = pg_strdup(value);
 
-	if (var->value)
-		free(var->value);
-	var->value = val;
-	var->is_numeric = false;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = val;
+	var->value.type = PGBT_NO_VALUE;
 
 	return true;
 }
 
-/* Assign a numeric value to a variable, creating it if need be */
+/* Assign a value to a variable, creating it if need be */
 /* Returns false on failure (bad name) */
 static bool
-putVariableNumber(CState *st, const char *context, char *name,
+putVariableValue(CState *st, const char *context, char *name,
 				  const PgBenchValue *value)
 {
 	Variable   *var;
@@ -1312,11 +1347,10 @@ putVariableNumber(CState *st, const char *context, char *name,
 	if (!var)
 		return false;
 
-	if (var->value)
-		free(var->value);
-	var->value = NULL;
-	var->is_numeric = true;
-	var->num_value = *value;
+	if (var->svalue)
+		free(var->svalue);
+	var->svalue = NULL;
+	var->value = *value;
 
 	return true;
 }
@@ -1329,7 +1363,7 @@ putVariableInt(CState *st, const char *context, char *name, int64 value)
 	PgBenchValue val;
 
 	setIntValue(&val, value);
-	return putVariableNumber(st, context, name, &val);
+	return putVariableValue(st, context, name, &val);
 }
 
 /*
@@ -1428,6 +1462,67 @@ getQueryParams(CState *st, const Command *command, const char **params)
 		params[i] = getVariable(st, command->argv[i + 1]);
 }
 
+static char *
+valueTypeName(PgBenchValue *pval)
+{
+	if (pval->type == PGBT_NO_VALUE)
+		return "none";
+	else if (pval->type == PGBT_NULL)
+		return "null";
+	else if (pval->type == PGBT_INT)
+		return "int";
+	else if (pval->type == PGBT_DOUBLE)
+		return "double";
+	else if (pval->type == PGBT_BOOLEAN)
+		return "boolean";
+	else
+	{
+		/* internal error, should never get there */
+		Assert(false);
+		return NULL;
+	}
+}
+
+/* get a value as a boolean, or tell if there is a problem */
+static bool
+coerceToBool(PgBenchValue *pval, bool *bval)
+{
+	if (pval->type == PGBT_BOOLEAN)
+	{
+		*bval = pval->u.bval;
+		return true;
+	}
+	else /* NULL, INT or DOUBLE */
+	{
+		fprintf(stderr, "cannot coerce %s to boolean\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/*
+ * Return true or false from an expression for conditional purposes.
+ * Non zero numerical values are true, zero and NULL are false.
+ */
+static bool
+valueTruth(PgBenchValue *pval)
+{
+	switch (pval->type)
+	{
+		case PGBT_NULL:
+			return false;
+		case PGBT_BOOLEAN:
+			return pval->u.bval;
+		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;
+	}
+}
+
 /* get a value as an int, tell if there is a problem */
 static bool
 coerceToInt(PgBenchValue *pval, int64 *ival)
@@ -1437,11 +1532,10 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = pval->u.ival;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_DOUBLE)
 	{
 		double		dval = pval->u.dval;
 
-		Assert(pval->type == PGBT_DOUBLE);
 		if (dval < PG_INT64_MIN || PG_INT64_MAX < dval)
 		{
 			fprintf(stderr, "double to int overflow for %f\n", dval);
@@ -1450,6 +1544,11 @@ coerceToInt(PgBenchValue *pval, int64 *ival)
 		*ival = (int64) dval;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to int\n", valueTypeName(pval));
+		return false;
+	}
 }
 
 /* get a value as a double, or tell if there is a problem */
@@ -1461,12 +1560,32 @@ coerceToDouble(PgBenchValue *pval, double *dval)
 		*dval = pval->u.dval;
 		return true;
 	}
-	else
+	else if (pval->type == PGBT_INT)
 	{
-		Assert(pval->type == PGBT_INT);
 		*dval = (double) pval->u.ival;
 		return true;
 	}
+	else /* BOOLEAN or NULL */
+	{
+		fprintf(stderr, "cannot coerce %s to double\n", valueTypeName(pval));
+		return false;
+	}
+}
+
+/* assign a null value */
+static void
+setNullValue(PgBenchValue *pv)
+{
+	pv->type = PGBT_NULL;
+	pv->u.ival = 0;
+}
+
+/* assign a boolean value */
+static void
+setBoolValue(PgBenchValue *pv, bool bval)
+{
+	pv->type = PGBT_BOOLEAN;
+	pv->u.bval = bval;
 }
 /* assign an integer value */
 static void
@@ -1484,24 +1603,137 @@ setDoubleValue(PgBenchValue *pv, double dval)
 	pv->u.dval = dval;
 }
 
+static bool isLazyFunc(PgBenchFunction func)
+{
+	return func == PGBENCH_AND || func == PGBENCH_OR || func == PGBENCH_CASE;
+}
+
+/* lazy evaluation of some functions */
+static bool
+evalLazyFunc(TState *thread, CState *st,
+			 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
+{
+	PgBenchValue a1, a2;
+	bool ba1, ba2;
+
+	Assert(isLazyFunc(func) && args != NULL && args->next != NULL);
+
+	if (!evaluateExpr(thread, st, args->expr, &a1))
+		return false;
+
+	switch (func)
+	{
+	case PGBENCH_AND:
+		if (a1.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+
+		if (!coerceToBool(&a1, &ba1))
+			return false;
+
+		if (!ba1)
+		{
+			setBoolValue(retval, false);
+			return true;
+		}
+
+		if (!evaluateExpr(thread, st, args->next->expr, &a2))
+			return false;
+
+		if (a2.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+		else if (!coerceToBool(&a2, &ba2))
+			return false;
+		else
+		{
+			setBoolValue(retval, ba2);
+			return true;
+		}
+
+		return true;
+
+	case PGBENCH_OR:
+
+		if (a1.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+
+		if (!coerceToBool(&a1, &ba1))
+			return false;
+
+		if (ba1)
+		{
+			setBoolValue(retval, true);
+			return true;
+		}
+
+		if (!evaluateExpr(thread, st, args->next->expr, &a2))
+			return false;
+
+		if (a2.type == PGBT_NULL)
+		{
+			setNullValue(retval);
+			return true;
+		}
+		else if (!coerceToBool(&a2, &ba2))
+			return false;
+		else
+		{
+			setBoolValue(retval, ba2);
+			return true;
+		}
+
+	case PGBENCH_CASE:
+		/* when true */
+		if (valueTruth(&a1))
+			return evaluateExpr(thread, st, args->next->expr, retval);
+
+		/* final else case? */
+		if (args->next->next->next == NULL)
+			return evaluateExpr(thread, st, args->next->next->expr, retval);
+
+		/* no, another when, skip a value and proceed */
+		return evalLazyFunc(thread, st, PGBENCH_CASE, args->next->next, retval);
+
+	default:
+		/* internal error, cannot get here */
+		Assert(0);
+		break;
+	}
+	return false;
+}
+
 /* maximum number of function arguments */
 #define MAX_FARGS 16
 
 /*
- * Recursive evaluation of functions
+ * Recursive evaluation of standard functions,
+ * which do not require lazy evaluation.
  */
 static bool
-evalFunc(TState *thread, CState *st,
-		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
+evalStandardFunc(
+	TState *thread, CState *st,
+	PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
 {
 	/* evaluate all function arguments */
-	int			nargs = 0;
-	PgBenchValue vargs[MAX_FARGS];
+	int				nargs = 0;
+	PgBenchValue	vargs[MAX_FARGS];
 	PgBenchExprLink *l = args;
+	bool			has_null = false;
 
 	for (nargs = 0; nargs < MAX_FARGS && l != NULL; nargs++, l = l->next)
+	{
 		if (!evaluateExpr(thread, st, l->expr, &vargs[nargs]))
 			return false;
+		has_null |= vargs[nargs].type == PGBT_NULL;
+	}
 
 	if (l != NULL)
 	{
@@ -1510,6 +1742,13 @@ evalFunc(TState *thread, CState *st,
 		return false;
 	}
 
+	/* NULL arguments */
+	if (has_null && func != PGBENCH_IS && func != PGBENCH_DEBUG)
+	{
+		setNullValue(retval);
+		return true;
+	}
+
 	/* then evaluate function */
 	switch (func)
 	{
@@ -1519,6 +1758,10 @@ evalFunc(TState *thread, CState *st,
 		case PGBENCH_MUL:
 		case PGBENCH_DIV:
 		case PGBENCH_MOD:
+		case PGBENCH_EQ:
+		case PGBENCH_NE:
+		case PGBENCH_LE:
+		case PGBENCH_LT:
 			{
 				PgBenchValue *lval = &vargs[0],
 						   *rval = &vargs[1];
@@ -1554,6 +1797,22 @@ evalFunc(TState *thread, CState *st,
 							setDoubleValue(retval, ld / rd);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, ld == rd);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, ld != rd);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, ld <= rd);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, ld < rd);
+							return true;
+
 						default:
 							/* cannot get here */
 							Assert(0);
@@ -1582,6 +1841,22 @@ evalFunc(TState *thread, CState *st,
 							setIntValue(retval, li * ri);
 							return true;
 
+						case PGBENCH_EQ:
+							setBoolValue(retval, li == ri);
+							return true;
+
+						case PGBENCH_NE:
+							setBoolValue(retval, li != ri);
+							return true;
+
+						case PGBENCH_LE:
+							setBoolValue(retval, li <= ri);
+							return true;
+
+						case PGBENCH_LT:
+							setBoolValue(retval, li < ri);
+							return true;
+
 						case PGBENCH_DIV:
 						case PGBENCH_MOD:
 							if (ri == 0)
@@ -1622,6 +1897,45 @@ evalFunc(TState *thread, CState *st,
 				}
 			}
 
+			/* integer bitwise operators */
+		case PGBENCH_BITAND:
+		case PGBENCH_BITOR:
+		case PGBENCH_BITXOR:
+		case PGBENCH_LSHIFT:
+		case PGBENCH_RSHIFT:
+			{
+				int64 li, ri;
+
+				if (!coerceToInt(&vargs[0], &li) || !coerceToInt(&vargs[1], &ri))
+					return false;
+
+				if (func == PGBENCH_BITAND)
+					setIntValue(retval, li & ri);
+				else if (func == PGBENCH_BITOR)
+					setIntValue(retval, li | ri);
+				else if (func == PGBENCH_BITXOR)
+					setIntValue(retval, li ^ ri);
+				else if (func == PGBENCH_LSHIFT)
+					setIntValue(retval, li << ri);
+				else if (func == PGBENCH_RSHIFT)
+					setIntValue(retval, li >> ri);
+				else /* cannot get here */
+					Assert(0);
+
+				return true;
+			}
+
+			/* logical operators */
+		case PGBENCH_NOT:
+			{
+				bool b;
+				if (!coerceToBool(&vargs[0], &b))
+					return false;
+
+				setBoolValue(retval, !b);
+				return true;
+			}
+
 			/* no arguments */
 		case PGBENCH_PI:
 			setDoubleValue(retval, M_PI);
@@ -1660,13 +1974,16 @@ evalFunc(TState *thread, CState *st,
 				fprintf(stderr, "debug(script=%d,command=%d): ",
 						st->use_file, st->command + 1);
 
-				if (varg->type == PGBT_INT)
+				if (varg->type == PGBT_NULL)
+					fprintf(stderr, "null\n");
+				else if (varg->type == PGBT_BOOLEAN)
+					fprintf(stderr, "boolean %s\n", varg->u.bval ? "true" : "false");
+				else if (varg->type == PGBT_INT)
 					fprintf(stderr, "int " INT64_FORMAT "\n", varg->u.ival);
-				else
-				{
-					Assert(varg->type == PGBT_DOUBLE);
+				else if (varg->type == PGBT_DOUBLE)
 					fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval);
-				}
+				else /* internal error, unexpected type */
+					Assert(0);
 
 				*retval = *varg;
 
@@ -1676,6 +1993,8 @@ evalFunc(TState *thread, CState *st,
 			/* 1 double argument */
 		case PGBENCH_DOUBLE:
 		case PGBENCH_SQRT:
+		case PGBENCH_LN:
+		case PGBENCH_EXP:
 			{
 				double		dval;
 
@@ -1686,6 +2005,11 @@ evalFunc(TState *thread, CState *st,
 
 				if (func == PGBENCH_SQRT)
 					dval = sqrt(dval);
+				else if (func == PGBENCH_LN)
+					dval = log(dval);
+				else if (func == PGBENCH_EXP)
+					dval = exp(dval);
+				/* else is cast: do nothing */
 
 				setDoubleValue(retval, dval);
 				return true;
@@ -1868,6 +2192,16 @@ evalFunc(TState *thread, CState *st,
 				return true;
 			}
 
+		case PGBENCH_IS:
+			{
+				Assert(nargs == 2);
+				/* note: this simple implementation is more permissive than SQL */
+				setBoolValue(retval,
+							 vargs[0].type == vargs[1].type &&
+							 vargs[0].u.bval == vargs[1].u.bval);
+				return true;
+			}
+
 		default:
 			/* cannot get here */
 			Assert(0);
@@ -1876,6 +2210,17 @@ evalFunc(TState *thread, CState *st,
 	}
 }
 
+/* evaluate some function */
+static bool
+evalFunc(TState *thread, CState *st,
+		 PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
+{
+	if (isLazyFunc(func))
+		return evalLazyFunc(thread, st, func, args, retval);
+	else
+		return evalStandardFunc(thread, st, func, args, retval);
+}
+
 /*
  * Recursive evaluation of an expression in a pgbench script
  * using the current state of variables.
@@ -1904,10 +2249,10 @@ evaluateExpr(TState *thread, CState *st, PgBenchExpr *expr, PgBenchValue *retval
 					return false;
 				}
 
-				if (!makeVariableNumeric(var))
+				if (!makeVariableValue(var))
 					return false;
 
-				*retval = var->num_value;
+				*retval = var->value;
 				return true;
 			}
 
@@ -2479,7 +2824,7 @@ doCustom(TState *thread, CState *st, StatsData *agg)
 								break;
 							}
 
-							if (!putVariableNumber(st, argv[0], argv[1], &result))
+							if (!putVariableValue(st, argv[0], argv[1], &result))
 							{
 								commandFailed(st, "assignment of meta-command 'set' failed");
 								st->state = CSTATE_ABORTED;
@@ -4582,16 +4927,16 @@ main(int argc, char **argv)
 			{
 				Variable   *var = &state[0].variables[j];
 
-				if (var->is_numeric)
+				if (var->value.type != PGBT_NO_VALUE)
 				{
-					if (!putVariableNumber(&state[i], "startup",
-										   var->name, &var->num_value))
+					if (!putVariableValue(&state[i], "startup",
+										   var->name, &var->value))
 						exit(1);
 				}
 				else
 				{
 					if (!putVariable(&state[i], "startup",
-									 var->name, var->value))
+									 var->name, var->svalue))
 						exit(1);
 				}
 			}
diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h
index ce3c260..0705ccd 100644
--- a/src/bin/pgbench/pgbench.h
+++ b/src/bin/pgbench/pgbench.h
@@ -33,8 +33,11 @@ union YYSTYPE;
  */
 typedef enum
 {
+	PGBT_NO_VALUE,
+	PGBT_NULL,
 	PGBT_INT,
-	PGBT_DOUBLE
+	PGBT_DOUBLE,
+	PGBT_BOOLEAN
 	/* add other types here */
 } PgBenchValueType;
 
@@ -45,6 +48,7 @@ typedef struct
 	{
 		int64		ival;
 		double		dval;
+		bool		bval;
 		/* add other types here */
 	}			u;
 } PgBenchValue;
@@ -73,11 +77,27 @@ typedef enum PgBenchFunction
 	PGBENCH_DOUBLE,
 	PGBENCH_PI,
 	PGBENCH_SQRT,
+	PGBENCH_LN,
+	PGBENCH_EXP,
 	PGBENCH_RANDOM,
 	PGBENCH_RANDOM_GAUSSIAN,
 	PGBENCH_RANDOM_EXPONENTIAL,
 	PGBENCH_RANDOM_ZIPFIAN,
-	PGBENCH_POW
+	PGBENCH_POW,
+	PGBENCH_AND,
+	PGBENCH_OR,
+	PGBENCH_NOT,
+	PGBENCH_BITAND,
+	PGBENCH_BITOR,
+	PGBENCH_BITXOR,
+	PGBENCH_LSHIFT,
+	PGBENCH_RSHIFT,
+	PGBENCH_EQ,
+	PGBENCH_NE,
+	PGBENCH_LE,
+	PGBENCH_LT,
+	PGBENCH_IS,
+	PGBENCH_CASE
 } PgBenchFunction;
 
 typedef struct PgBenchExpr PgBenchExpr;
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 3dd080e..a49be17 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -211,10 +211,13 @@ COMMIT;
 
 # test expressions
 pgbench(
-	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808',
+	'-t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dminint=-9223372036854775808 -Dn=null -Dt=t -Df=of -Dd=1.0',
 	0,
 	[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
-	[   qr{command=4.: int 4\b},
+	[   qr{command=1.: int 1\d\b},
+	    qr{command=2.: int 1\d\d\b},
+	    qr{command=3.: int 1\d\d\d\b},
+	    qr{command=4.: int 4\b},
 		qr{command=5.: int 5\b},
 		qr{command=6.: int 6\b},
 		qr{command=7.: int 7\b},
@@ -223,51 +226,61 @@ pgbench(
 		qr{command=10.: int 10\b},
 		qr{command=11.: int 11\b},
 		qr{command=12.: int 12\b},
-		qr{command=13.: double 13\b},
-		qr{command=14.: double 14\b},
 		qr{command=15.: double 15\b},
 		qr{command=16.: double 16\b},
 		qr{command=17.: double 17\b},
-		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=23.: int [1-9]\b},
-		qr{command=24.: double -27\b},
-		qr{command=25.: double 1024\b},
-		qr{command=26.: double 1\b},
-		qr{command=27.: double 1\b},
-		qr{command=28.: double -0.125\b},
-		qr{command=29.: double -0.125\b},
-		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=18.: int 9223372036854775807\b},
+		qr{command=20.: int [1-9]\b},
+		qr{command=21.: double -27\b},
+		qr{command=22.: double 1024\b},
+		qr{command=23.: double 1\b},
+		qr{command=24.: double 1\b},
+		qr{command=25.: double -0.125\b},
+		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=30.: boolean false\b},
+		qr{command=31.: boolean true\b},
+		qr{command=32.: int 32\b},
+		qr{command=33.: int 33\b},
+		qr{command=34.: double 34\b},
+		qr{command=35.: int 35\b},
+		qr{command=36.: int 36\b},
+		qr{command=37.: double 37\b},
+		qr{command=38.: int 38\b},
+		qr{command=39.: int 39\b},
+		qr{command=40.: boolean true\b},
+		qr{command=41.: null\b},
+		qr{command=42.: null\b},
+		qr{command=43.: boolean true\b},
+		qr{command=44.: boolean true\b},
+		qr{command=45.: boolean true\b},
+		qr{command=46.: int 46\b},
+		qr{command=47.: boolean true\b},
+		qr{command=48.: boolean true\b},
 	],
 	'pgbench expressions',
 	{   '001_pgbench_expressions' => q{-- integer functions
-\set i1 debug(random(1, 100))
-\set i2 debug(random_exponential(1, 100, 10.0))
-\set i3 debug(random_gaussian(1, 100, 10.0))
+\set i1 debug(random(10, 19))
+\set i2 debug(random_exponential(100, 199, 10.0))
+\set i3 debug(random_gaussian(1000, 1999, 10.0))
 \set i4 debug(abs(-4))
 \set i5 debug(greatest(5, 4, 3, 2))
 \set i6 debug(11 + least(-5, -4, -3, -2))
 \set i7 debug(int(7.3))
--- integer operators
-\set i8 debug(17 / 5 + 5)
-\set i9 debug(- (3 * 4 - 3) / -1 + 3 % -1)
+-- integer arithmetic and bit-wise operators
+\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
+\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
 \set ia debug(10 + (0 + 0 * 0 - 0 / 1))
 \set ib debug(:ia + :scale)
-\set ic debug(64 % 13)
--- double functions
-\set d1 debug(sqrt(3.0) * abs(-0.8E1))
-\set d2 debug(double(1 + 1) * 7)
+\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
+-- double functions and operators
+\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
+\set d2 debug(double(1 + 1) * (-75.0 / :foo))
 \set pi debug(pi() * 4.9)
-\set d4 debug(greatest(4, 2, -1.17) * 4.0)
+\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
 \set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
--- double operators
-\set d6 debug((0.5 * 12.1 - 0.05) * (31.0 / 10))
-\set d7 debug(11.1 + 7.9)
-\set d8 debug(:foo * -2)
 -- forced overflow
 \set maxint debug(:minint - 1)
 -- reset a variable
@@ -284,8 +297,50 @@ pgbench(
 \set powernegd2 debug(power(-5.0,-5.0))
 \set powerov debug(pow(9223372036854775807, 2))
 \set powerov2 debug(pow(10,30))
+-- comparisons and logical operations
+\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
+\set c1 debug(0 = 1 Or 1.0 = 1)
+\set c4 debug(case when 0 < 1 then 32 else 0 end)
+\set c5 debug(case when true then 33 else 0 end)
+\set c6 debug(case when false THEN -1 when 1 = 1 then 13 + 19 + 2.0 end )
+\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 35 else 0 end)
+\set c8 debug(CASE \
+                WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
+                     (0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
+                  THEN 36 \
+                  ELSE 0 \
+              END)
+\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 12.3333334 END)
+\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 38 end)
+\set cb debug(10 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
+\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
+    NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
+    NOT (NOT TRUE))
+-- NULL value and associated operators
+\set n0 debug(NULL + NULL * exp(NULL))
+\set n1 debug(:n0)
+\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
+\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
+\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
+\set n5 debug(CASE WHEN :n IS NULL THEN 46 ELSE NULL END)
+-- use a variables of all types
+\set n6 debug(:n IS NULL AND NOT :f AND :t)
+-- conditional truth
+\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
+-- substitute variables of all possible types
+\set v0 NULL
+\set v1 TRUE
+\set v2 5432
+\set v3 -54.21E-2
+SELECT :v0, :v1, :v2, :v3;
 } });
 
+=head
+
+} });
+
+=cut
+
 # backslash commands
 pgbench(
 	'-t 1', 0,
@@ -404,8 +459,42 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 		q{\set i random_zipfian(0, 10, 1000000)} ],
 	[   'set non numeric value',                     0,
 		[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1} ],
-	[ 'set no expression',    1, [qr{syntax error}],      q{\set i} ],
-	[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
+	[ 'set no expression',
+		1,
+		[qr{syntax error}],
+		q{\set i} ],
+	[ 'set missing argument',
+		1,
+		[qr{missing argument}i],
+		q{\set} ],
+	[ 'set not a bool',
+		0,
+		[ qr{cannot coerce double to boolean} ],
+		q{\set b NOT 0.0} ],
+	[ 'set not an int',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set i TRUE + 2} ],
+	[ 'set not an double',
+		0,
+		[ qr{cannot coerce boolean to double} ],
+		q{\set d ln(TRUE)} ],
+	[ 'set case error',
+		1,
+		[ qr{syntax error in command "set"} ],
+		q{\set i CASE TRUE THEN 1 ELSE 0 END} ],
+	[ 'set random error',
+		0,
+		[ qr{cannot coerce boolean to int} ],
+		q{\set b random(FALSE, TRUE)} ],
+	[ 'set number of args mismatch',
+		1,
+		[ qr{unexpected number of arguments} ],
+		q{\set d ln(1.0, 2.0))} ],
+	[ 'set at least one arg',
+		1,
+		[ qr{at least one argument expected} ],
+		q{\set i greatest())} ],
 
 	# SETSHELL
 	[   'setshell not an int',                0,
@@ -427,7 +516,10 @@ SELECT LEAST(:i, :i, :i, :i, :i, :i, :i, :i, :i, :i, :i);
 	# MISC
 	[   'misc invalid backslash command',         1,
 		[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} ],
-	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],);
+	[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
+	[ 'bad boolean', 0, [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} ],
+    );
+
 
 for my $e (@errors)
 {
@@ -435,7 +527,7 @@ for my $e (@errors)
 	my $n = '001_pgbench_error_' . $name;
 	$n =~ s/ /_/g;
 	pgbench(
-		'-n -t 1 -Dfoo=bla -M prepared',
+		'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX -M prepared',
 		$status,
 		[ $status ? qr{^$} : qr{processed: 0/1} ],
 		$re,
#91Teodor Sigaev
teodor@sigaev.ru
In reply to: Fabien COELHO (#90)
Re: [HACKERS] pgbench more operators & functions

Thank you, pushed

Fabien COELHO wrote:

SQL doesn't evaluate unneeded arguments:

Here is a version with some lazy evaluation for and, or & case.

v23 is a rebase.

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

#92Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Teodor Sigaev (#91)
Re: [HACKERS] pgbench more operators & functions

Thank you, pushed

Thanks!

--
Fabien.

#93Tom Lane
tgl@sss.pgh.pa.us
In reply to: Teodor Sigaev (#91)
Re: [HACKERS] pgbench more operators & functions

Teodor Sigaev <teodor@sigaev.ru> writes:

Thank you, pushed

Some of the Windows buildfarm members aren't too happy with this.

regards, tom lane

#94Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Tom Lane (#93)
1 attachment(s)
Re: [HACKERS] pgbench more operators & functions

Some of the Windows buildfarm members aren't too happy with this.

Indeed.

Windows prettyprinting of double inserts a spurious "0" at the beginning
of the exponent. Makes it look like an octal.

Here is a patch to fix it, which I cannot test on Windows.

--
Fabien.

Attachments:

pgbench-test-fix-1.patchtext/x-diff; name=pgbench-test-fix-1.patchDownload
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index e579334..a8b2962 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},