| %{ |
| /*------------------------------------------------------------------------- |
| * |
| * exprparse.y |
| * bison grammar for a simple expression syntax |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * src/bin/pgbench/exprparse.y |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| #include "postgres_fe.h" |
| |
| #include "pgbench.h" |
| |
| #define PGBENCH_NARGS_VARIABLE (-1) |
| #define PGBENCH_NARGS_CASE (-2) |
| #define PGBENCH_NARGS_HASH (-3) |
| #define PGBENCH_NARGS_PERMUTE (-4) |
| |
| 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); |
| |
| %} |
| |
| %pure-parser |
| %expect 0 |
| %name-prefix="expr_yy" |
| |
| %parse-param {yyscan_t yyscanner} |
| %lex-param {yyscan_t yyscanner} |
| |
| %union |
| { |
| int64 ival; |
| double dval; |
| bool bval; |
| char *str; |
| PgBenchExpr *expr; |
| PgBenchExprList *elist; |
| } |
| |
| %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 NULL_CONST INTEGER_CONST MAXINT_PLUS_ONE_CONST DOUBLE_CONST |
| %token 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 UNARY |
| |
| %% |
| |
| result: expr { expr_parse_result = $1; } |
| |
| elist: { $$ = NULL; } |
| | expr { $$ = make_elist($1, NULL); } |
| | elist ',' expr { $$ = make_elist($3, $1); } |
| ; |
| |
| expr: '(' expr ')' { $$ = $2; } |
| | '+' expr %prec UNARY { $$ = $2; } |
| /* unary minus "-x" implemented as "0 - x" */ |
| | '-' expr %prec UNARY { $$ = make_op(yyscanner, "-", |
| make_integer_constant(0), $2); } |
| /* special PG_INT64_MIN handling, only after a unary minus */ |
| | '-' MAXINT_PLUS_ONE_CONST %prec UNARY |
| { $$ = make_integer_constant(PG_INT64_MIN); } |
| /* 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, "mod", $1, $3); } |
| | expr '<' expr { $$ = make_op(yyscanner, "<", $1, $3); } |
| | expr LE_OP expr { $$ = make_op(yyscanner, "<=", $1, $3); } |
| | expr '>' expr { $$ = make_op(yyscanner, "<", $3, $1); } |
| | expr GE_OP expr { $$ = make_op(yyscanner, "<=", $3, $1); } |
| | expr '=' expr { $$ = make_op(yyscanner, "=", $1, $3); } |
| | expr NE_OP expr { $$ = make_op(yyscanner, "<>", $1, $3); } |
| | expr '&' expr { $$ = make_op(yyscanner, "&", $1, $3); } |
| | expr '|' expr { $$ = make_op(yyscanner, "|", $1, $3); } |
| | expr '#' expr { $$ = make_op(yyscanner, "#", $1, $3); } |
| | expr LS_OP expr { $$ = make_op(yyscanner, "<<", $1, $3); } |
| | expr RS_OP expr { $$ = make_op(yyscanner, ">>", $1, $3); } |
| | expr AND_OP expr { $$ = make_op(yyscanner, "!and", $1, $3); } |
| | expr OR_OP expr { $$ = make_op(yyscanner, "!or", $1, $3); } |
| /* IS variants */ |
| | expr ISNULL_OP { $$ = make_op(yyscanner, "!is", $1, make_null_constant()); } |
| | expr NOTNULL_OP { |
| $$ = make_uop(yyscanner, "!not", |
| make_op(yyscanner, "!is", $1, make_null_constant())); |
| } |
| | expr IS_OP NULL_CONST { $$ = make_op(yyscanner, "!is", $1, make_null_constant()); } |
| | expr IS_OP NOT_OP NULL_CONST |
| { |
| $$ = make_uop(yyscanner, "!not", |
| make_op(yyscanner, "!is", $1, make_null_constant())); |
| } |
| | expr IS_OP BOOLEAN_CONST |
| { |
| $$ = make_op(yyscanner, "!is", $1, make_boolean_constant($3)); |
| } |
| | expr IS_OP NOT_OP BOOLEAN_CONST |
| { |
| $$ = make_uop(yyscanner, "!not", |
| make_op(yyscanner, "!is", $1, make_boolean_constant($4))); |
| } |
| /* constants */ |
| | NULL_CONST { $$ = make_null_constant(); } |
| | BOOLEAN_CONST { $$ = make_boolean_constant($1); } |
| | INTEGER_CONST { $$ = make_integer_constant($1); } |
| | DOUBLE_CONST { $$ = make_double_constant($1); } |
| /* misc */ |
| | VARIABLE { $$ = make_variable($1); } |
| | function '(' elist ')' { $$ = make_func(yyscanner, $1, $3); } |
| | case_control { $$ = $1; } |
| ; |
| |
| when_then_list: |
| when_then_list WHEN_KW expr THEN_KW expr { $$ = make_elist($5, make_elist($3, $1)); } |
| | WHEN_KW expr THEN_KW expr { $$ = make_elist($4, make_elist($2, NULL)); } |
| |
| case_control: |
| CASE_KW when_then_list END_KW { $$ = make_case(yyscanner, $2, make_null_constant()); } |
| | CASE_KW when_then_list ELSE_KW expr END_KW { $$ = make_case(yyscanner, $2, $4); } |
| |
| function: FUNCTION { $$ = find_func(yyscanner, $1); pg_free($1); } |
| ; |
| |
| %% |
| |
| static PgBenchExpr * |
| make_null_constant(void) |
| { |
| PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); |
| |
| expr->etype = ENODE_CONSTANT; |
| expr->u.constant.type = PGBT_NULL; |
| expr->u.constant.u.ival = 0; |
| return expr; |
| } |
| |
| static PgBenchExpr * |
| make_integer_constant(int64 ival) |
| { |
| PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); |
| |
| expr->etype = ENODE_CONSTANT; |
| expr->u.constant.type = PGBT_INT; |
| expr->u.constant.u.ival = ival; |
| return expr; |
| } |
| |
| static PgBenchExpr * |
| make_double_constant(double dval) |
| { |
| PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); |
| |
| expr->etype = ENODE_CONSTANT; |
| expr->u.constant.type = PGBT_DOUBLE; |
| expr->u.constant.u.dval = 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) |
| { |
| PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); |
| |
| expr->etype = ENODE_VARIABLE; |
| expr->u.variable.varname = varname; |
| return expr; |
| } |
| |
| /* binary operators */ |
| static PgBenchExpr * |
| make_op(yyscan_t yyscanner, const char *operator, |
| PgBenchExpr *lexpr, PgBenchExpr *rexpr) |
| { |
| return make_func(yyscanner, find_func(yyscanner, 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, "!..." for special internal functions |
| * - nargs: number of arguments. Special cases: |
| * - PGBENCH_NARGS_VARIABLE is a special value for least & greatest |
| * meaning #args >= 1; |
| * - PGBENCH_NARGS_CASE is for the "CASE WHEN ..." function, which |
| * has #args >= 3 and odd; |
| * - PGBENCH_NARGS_HASH is for hash functions, which have one required |
| * and one optional argument; |
| * - tag: function identifier from PgBenchFunction enum |
| */ |
| static const struct |
| { |
| const char *fname; |
| int nargs; |
| PgBenchFunction tag; |
| } PGBENCH_FUNCTIONS[] = |
| { |
| /* parsed as operators, executed as functions */ |
| { |
| "+", 2, PGBENCH_ADD |
| }, |
| { |
| "-", 2, PGBENCH_SUB |
| }, |
| { |
| "*", 2, PGBENCH_MUL |
| }, |
| { |
| "/", 2, PGBENCH_DIV |
| }, |
| { |
| "mod", 2, PGBENCH_MOD |
| }, |
| /* actual functions */ |
| { |
| "abs", 1, PGBENCH_ABS |
| }, |
| { |
| "least", PGBENCH_NARGS_VARIABLE, PGBENCH_LEAST |
| }, |
| { |
| "greatest", PGBENCH_NARGS_VARIABLE, PGBENCH_GREATEST |
| }, |
| { |
| "debug", 1, PGBENCH_DEBUG |
| }, |
| { |
| "pi", 0, PGBENCH_PI |
| }, |
| { |
| "sqrt", 1, PGBENCH_SQRT |
| }, |
| { |
| "ln", 1, PGBENCH_LN |
| }, |
| { |
| "exp", 1, PGBENCH_EXP |
| }, |
| { |
| "int", 1, PGBENCH_INT |
| }, |
| { |
| "double", 1, PGBENCH_DOUBLE |
| }, |
| { |
| "random", 2, PGBENCH_RANDOM |
| }, |
| { |
| "random_gaussian", 3, PGBENCH_RANDOM_GAUSSIAN |
| }, |
| { |
| "random_exponential", 3, PGBENCH_RANDOM_EXPONENTIAL |
| }, |
| { |
| "random_zipfian", 3, PGBENCH_RANDOM_ZIPFIAN |
| }, |
| { |
| "pow", 2, PGBENCH_POW |
| }, |
| { |
| "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", PGBENCH_NARGS_CASE, PGBENCH_CASE |
| }, |
| { |
| "hash", PGBENCH_NARGS_HASH, PGBENCH_HASH_MURMUR2 |
| }, |
| { |
| "hash_murmur2", PGBENCH_NARGS_HASH, PGBENCH_HASH_MURMUR2 |
| }, |
| { |
| "hash_fnv1a", PGBENCH_NARGS_HASH, PGBENCH_HASH_FNV1A |
| }, |
| { |
| "permute", PGBENCH_NARGS_PERMUTE, PGBENCH_PERMUTE |
| }, |
| /* keep as last array element */ |
| { |
| NULL, 0, 0 |
| } |
| }; |
| |
| /* |
| * Find a function from its name |
| * |
| * return the index of the function from the PGBENCH_FUNCTIONS array |
| * or fail if the function is unknown. |
| */ |
| static int |
| find_func(yyscan_t yyscanner, const char *fname) |
| { |
| int i = 0; |
| |
| while (PGBENCH_FUNCTIONS[i].fname) |
| { |
| if (pg_strcasecmp(fname, PGBENCH_FUNCTIONS[i].fname) == 0) |
| return i; |
| i++; |
| } |
| |
| expr_yyerror_more(yyscanner, "unexpected function name", fname); |
| |
| /* not reached */ |
| return -1; |
| } |
| |
| /* Expression linked list builder */ |
| static PgBenchExprList * |
| make_elist(PgBenchExpr *expr, PgBenchExprList *list) |
| { |
| PgBenchExprLink *cons; |
| |
| if (list == NULL) |
| { |
| list = pg_malloc(sizeof(PgBenchExprList)); |
| list->head = NULL; |
| list->tail = NULL; |
| } |
| |
| cons = pg_malloc(sizeof(PgBenchExprLink)); |
| cons->expr = expr; |
| cons->next = NULL; |
| |
| if (list->head == NULL) |
| list->head = cons; |
| else |
| list->tail->next = cons; |
| |
| list->tail = cons; |
| |
| return list; |
| } |
| |
| /* Return the length of an expression list */ |
| static int |
| elist_length(PgBenchExprList *list) |
| { |
| PgBenchExprLink *link = list != NULL ? list->head : NULL; |
| int len = 0; |
| |
| for (; link != NULL; link = link->next) |
| len++; |
| |
| return len; |
| } |
| |
| /* Build function call expression */ |
| static PgBenchExpr * |
| make_func(yyscan_t yyscanner, int fnumber, PgBenchExprList *args) |
| { |
| int len = elist_length(args); |
| |
| PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); |
| |
| Assert(fnumber >= 0); |
| |
| /* validate arguments number including few special cases */ |
| switch (PGBENCH_FUNCTIONS[fnumber].nargs) |
| { |
| /* check at least one arg for least & greatest */ |
| case PGBENCH_NARGS_VARIABLE: |
| if (len == 0) |
| expr_yyerror_more(yyscanner, "at least one argument expected", |
| PGBENCH_FUNCTIONS[fnumber].fname); |
| break; |
| |
| /* case (when ... then ...)+ (else ...)? end */ |
| case PGBENCH_NARGS_CASE: |
| /* '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"); |
| break; |
| |
| /* hash functions with optional seed argument */ |
| case PGBENCH_NARGS_HASH: |
| if (len < 1 || len > 2) |
| expr_yyerror_more(yyscanner, "unexpected number of arguments", |
| PGBENCH_FUNCTIONS[fnumber].fname); |
| |
| if (len == 1) |
| { |
| PgBenchExpr *var = make_variable("default_seed"); |
| args = make_elist(var, args); |
| } |
| break; |
| |
| /* pseudorandom permutation function with optional seed argument */ |
| case PGBENCH_NARGS_PERMUTE: |
| if (len < 2 || len > 3) |
| expr_yyerror_more(yyscanner, "unexpected number of arguments", |
| PGBENCH_FUNCTIONS[fnumber].fname); |
| |
| if (len == 2) |
| { |
| PgBenchExpr *var = make_variable("default_seed"); |
| args = make_elist(var, args); |
| } |
| break; |
| |
| /* common case: positive arguments number */ |
| default: |
| Assert(PGBENCH_FUNCTIONS[fnumber].nargs >= 0); |
| |
| if (PGBENCH_FUNCTIONS[fnumber].nargs != len) |
| expr_yyerror_more(yyscanner, "unexpected number of arguments", |
| PGBENCH_FUNCTIONS[fnumber].fname); |
| } |
| |
| expr->etype = ENODE_FUNCTION; |
| expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag; |
| |
| /* only the link is used, the head/tail is not useful anymore */ |
| expr->u.function.args = args != NULL ? args->head : NULL; |
| if (args) |
| pg_free(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 |
| * its token symbols. If these files ever grow large enough to be |
| * worth compiling separately, that could be fixed; but for now it |
| * seems like useless complication. |
| */ |
| |
| /* First, get rid of "#define yyscan_t" from pgbench.h */ |
| #undef yyscan_t |
| /* ... and the yylval macro, which flex will have its own definition for */ |
| #undef yylval |
| |
| #include "exprscan.c" |