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: teo...@sigaev.ru
WWW: http://www.sigaev.ru/
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 <> 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><></literal></entry>
+ <entry>is not equal</entry>
+ <entry><literal>5 <> 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><</literal></entry>
+ <entry>lower than</entry>
+ <entry><literal>5 < 4</literal></entry>
+ <entry><literal>FALSE</literal></entry>
+ </row>
+ <row>
+ <entry><literal><=</literal></entry>
+ <entry>lower or equal</entry>
+ <entry><literal>5 <= 4</literal></entry>
+ <entry><literal>FALSE</literal></entry>
+ </row>
+ <row>
+ <entry><literal>></literal></entry>
+ <entry>greater than</entry>
+ <entry><literal>5 > 4</literal></entry>
+ <entry><literal>TRUE</literal></entry>
+ </row>
+ <row>
+ <entry><literal>>=</literal></entry>
+ <entry>greater or equal</entry>
+ <entry><literal>5 >= 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>&</literal></entry>
+ <entry>integer bitwise AND</entry>
+ <entry><literal>1 & 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><<</literal></entry>
+ <entry>integer bitwise shift left</entry>
+ <entry><literal>1 << 2</literal></entry>
+ <entry><literal>4</literal></entry>
+ </row>
+ <row>
+ <entry><literal>>></literal></entry>
+ <entry>integer bitwise shift right</entry>
+ <entry><literal>8 >> 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,