Commit bc7fa0c1 authored by Teodor Sigaev's avatar Teodor Sigaev

Improve scripting language in pgbench

Added:
 - variable now might contain integer, double, boolean and null values
 - functions ln, exp
 - logical AND/OR/NOT
 - bitwise AND/OR/NOT/XOR
 - bit right/left shift
 - comparison operators
 - IS [NOT] (NULL|TRUE|FALSE)
 - conditional choice (in form of when/case/then)

New operations and functions allow to implement more complicated test scenario.

Author: Fabien Coelho with minor editorization by me
Reviewed-By: Pavel Stehule, Jeevan Ladhe, me
Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.10.1604030742390.31618@sto
parent 63008b19
......@@ -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>
......
......@@ -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
},
......@@ -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,16 @@ 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);
/* 'else' branch is always present, but could be a NULL-constant */
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 +459,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
......
......@@ -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;
......
......@@ -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,144 @@ 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);
/* args points to first condition */
if (!evaluateExpr(thread, st, args->expr, &a1))
return false;
/* second condition for AND/OR and corresponding branch for CASE */
args = args->next;
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->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->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, execute branch */
if (valueTruth(&a1))
return evaluateExpr(thread, st, args->expr, retval);
/* now args contains next condition or final else expression */
args = args->next;
/* final else case? */
if (args->next == NULL)
return evaluateExpr(thread, st, args->expr, retval);
/* no, another when, proceed */
return evalLazyFunc(thread, st, PGBENCH_CASE, args, 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 +1749,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 +1765,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 +1804,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 +1848,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 +1904,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 +1981,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 +2000,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 +2012,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 +2199,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 +2217,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 +2256,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 +2831,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 +4934,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);
}
}
......
......@@ -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;
......
......@@ -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,55 @@ 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)
-- lazy evaluation
\set zy 0
\set yz debug(case when :zy = 0 then -1 else (1 / :zy) end)
\set yz debug(case when :zy = 0 or (1 / :zy) < 0 then -1 else (1 / :zy) end)
\set yz debug(case when :zy > 0 and (1 / :zy) < 0 then (1 / :zy) else 1 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 +464,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 +521,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 +532,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,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment