Improve scripting language in pgbench
authorTeodor Sigaev
Tue, 9 Jan 2018 15:02:04 +0000 (18:02 +0300)
committerTeodor Sigaev
Tue, 9 Jan 2018 15:02:04 +0000 (18:02 +0300)
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

doc/src/sgml/ref/pgbench.sgml
src/bin/pgbench/exprparse.y
src/bin/pgbench/exprscan.l
src/bin/pgbench/pgbench.c
src/bin/pgbench/pgbench.h
src/bin/pgbench/t/001_pgbench_with_server.pl

index 1519fe78ef95a8684bc1f9f5e689b567de8b911a..3dd492cec11a47ab04805d7c4cc64c63c45339e7 100644 (file)
@@ -904,14 +904,32 @@ pgbench  options  d
      
       Sets variable varname to a value calculated
       from expression.
-      The expression may contain integer constants such as 5432,
+      The expression may contain the NULL constant,
+      boolean constants TRUE and FALSE,
+      integer constants such as 5432,
       double constants such as 3.14159,
       references to variables :variablename,
-      unary operators (+-) and binary operators
-      (+-*/,
-      %) with their usual precedence and associativity,
-      function calls, and
-      parentheses.
+      operators
+      with their usual SQL precedence and associativity,
+      function calls,
+      SQL CASE generic conditional
+      expressions and parentheses.
+     
+
+     
+      Functions and most operators return NULL on
+      NULL input.
+     
+
+     
+      For conditional purposes, non zero numerical values are
+      TRUE, zero numerical values and NULL
+      are FALSE.
+     
+
+     
+      When no final ELSE clause is provided to a
+      CASE, the default value is NULL.
      
 
      
@@ -920,6 +938,7 @@ pgbench  options  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
 
     
    
@@ -996,6 +1015,177 @@ pgbench  options  d
   
  
 
+  Built-In Operators
+
+  
+   The arithmetic, bitwise, comparison and logical operators listed in
+    are built into pgbench
+   and may be used in expressions appearing in
+   \set.
+  
+
+  
+   pgbench Operators by increasing precedence
+   
+    
+     
+      Operator
+      Description
+      Example
+      Result
+     
+    
+    
+     
+      OR
+      logical or
+      5 or 0
+      TRUE
+     
+     
+      AND
+      logical and
+      3 and 0
+      FALSE
+     
+     
+      NOT
+      logical not
+      not false
+      TRUE
+     
+     
+      IS [NOT] (NULL|TRUE|FALSE)
+      value tests
+      1 is null
+      FALSE
+     
+     
+      ISNULL|NOTNULL
+      null tests
+      1 notnull
+      TRUE
+     
+     
+      =
+      is equal
+      5 = 4
+      FALSE
+     
+     
+      <>
+      is not equal
+      5 <> 4
+      TRUE
+     
+     
+      !=
+      is not equal
+      5 != 5
+      FALSE
+     
+     
+      <
+      lower than
+      5 < 4
+      FALSE
+     
+     
+      <=
+      lower or equal
+      5 <= 4
+      FALSE
+     
+     
+      >
+      greater than
+      5 > 4
+      TRUE
+     
+     
+      >=
+      greater or equal
+      5 >= 4
+      TRUE
+     
+     
+      |
+      integer bitwise OR
+      1 | 2
+      3
+     
+     
+      #
+      integer bitwise XOR
+      1 # 3
+      2
+     
+     
+      &
+      integer bitwise AND
+      1 & 3
+      1
+     
+     
+      ~
+      integer bitwise NOT
+      ~ 1
+      -2
+     
+     
+      <<
+      integer bitwise shift left
+      1 << 2
+      4
+     
+     
+      >>
+      integer bitwise shift right
+      8 >> 2
+      2
+     
+     
+      +
+      addition
+      5 + 4
+      9
+     
+     
+      -
+      substraction
+      3 - 2.0
+      1.0
+     
+     
+      *
+      multiplication
+      5 * 4
+      20
+     
+     
+      /
+      division (integer truncates the results)
+      5 / 3
+      1
+     
+     
+      %
+      modulo
+      3 % 2
+      1
+     
+     
+      -
+      opposite
+      - 2.0
+      -2.0
+     
+    
+   
+  
+
  
   Built-In Functions
 
@@ -1041,6 +1231,13 @@ pgbench  options  d
        double(5432)
        5432.0
       
+      
+       exp(x)
+       double
+       exponential
+       exp(1.0)
+       2.718281828459045
+      
       
        greatest(a [, ... ] )
        double if any a is double, else integer
@@ -1062,6 +1259,20 @@ pgbench  options  d
        least(5, 4, 3, 2.1)
        2.1
       
+      
+       ln(x)
+       double
+       natural logarithm
+       ln(2.718281828459045)
+       1.0
+      
+      
+       mod(ibj)
+       integer
+       modulo
+       mod(54, 32)
+       22
+      
       
        pi()
        double
index 26494fdd9f1ee085183fdb1ca4404bf9137020d4..e23ca517864ef79395c2ae8fc832237ed2b64a97 100644 (file)
 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
-%type  expr
+%type  elist when_then_list
+%type  expr case_control
 %type  INTEGER_CONST function
 %type  DOUBLE_CONST
+%type  BOOLEAN_CONST
 %type  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
index b86e77a7ea9beb2a48cdad07cf3df53657a2507c..5c1bd881283d2f05e27deb10d9e64f822631a863 100644 (file)
@@ -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;
index fc2c7342ed807caf4df8bc7ce25c82fcf6f6d772..31ea6ca06e0e98159d2b6cc3677b4853654f328f 100644 (file)
@@ -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);
                }
            }
index ce3c2609884cfddacb4cbcaa80d0fe92cd9b57e1..0705ccdf0d66f79a0a2d64ce18d5ed82caae1035 100644 (file)
@@ -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;
index 3dd080e6e686a0419b83f6ea8baa67e5ccc3c809..e57933491493cd7aa235afc9da4bc020d3ec995a 100644 (file)
@@ -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,