mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
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
This commit is contained in:
parent
63008b19ee
commit
bc7fa0c15c
@ -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>
|
||||
|
@ -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,25 +82,88 @@ 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(~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); }
|
||||
/* 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,
|
||||
evalStandardFunc(
|
||||
TState *thread, CState *st,
|
||||
PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval)
|
||||
{
|
||||
/* evaluate all function arguments */
|
||||
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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user