diff options
Diffstat (limited to 'src/bin/pgbench')
-rw-r--r-- | src/bin/pgbench/.gitignore | 4 | ||||
-rw-r--r-- | src/bin/pgbench/Makefile | 54 | ||||
-rw-r--r-- | src/bin/pgbench/exprparse.c | 2200 | ||||
-rw-r--r-- | src/bin/pgbench/exprparse.y | 546 | ||||
-rw-r--r-- | src/bin/pgbench/exprscan.c | 2835 | ||||
-rw-r--r-- | src/bin/pgbench/exprscan.l | 463 | ||||
-rw-r--r-- | src/bin/pgbench/pgbench.c | 7854 | ||||
-rw-r--r-- | src/bin/pgbench/pgbench.h | 167 | ||||
-rw-r--r-- | src/bin/pgbench/t/001_pgbench_with_server.pl | 1447 | ||||
-rw-r--r-- | src/bin/pgbench/t/002_pgbench_no_server.pl | 387 |
10 files changed, 15957 insertions, 0 deletions
diff --git a/src/bin/pgbench/.gitignore b/src/bin/pgbench/.gitignore new file mode 100644 index 0000000..983a3cd --- /dev/null +++ b/src/bin/pgbench/.gitignore @@ -0,0 +1,4 @@ +/exprparse.c +/exprscan.c +/pgbench +/tmp_check/ diff --git a/src/bin/pgbench/Makefile b/src/bin/pgbench/Makefile new file mode 100644 index 0000000..f402fe7 --- /dev/null +++ b/src/bin/pgbench/Makefile @@ -0,0 +1,54 @@ +# src/bin/pgbench/Makefile + +PGFILEDESC = "pgbench - a simple program for running benchmark tests" +PGAPPICON = win32 + +subdir = src/bin/pgbench +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + $(WIN32RES) \ + exprparse.o \ + pgbench.o + +override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS) +LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport) + +ifneq ($(PORTNAME), win32) +override CFLAGS += $(PTHREAD_CFLAGS) +endif +LIBS += $(PTHREAD_LIBS) + + +all: pgbench + +pgbench: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils + $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X) + +# exprscan is compiled as part of exprparse +exprparse.o: exprscan.c + +distprep: exprparse.c exprscan.c + +install: all installdirs + $(INSTALL_PROGRAM) pgbench$(X) '$(DESTDIR)$(bindir)/pgbench$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(bindir)' + +uninstall: + rm -f '$(DESTDIR)$(bindir)/pgbench$(X)' + +clean distclean: + rm -f pgbench$(X) $(OBJS) + rm -rf tmp_check + +maintainer-clean: distclean + rm -f exprparse.c exprscan.c + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) diff --git a/src/bin/pgbench/exprparse.c b/src/bin/pgbench/exprparse.c new file mode 100644 index 0000000..b4e45dd --- /dev/null +++ b/src/bin/pgbench/exprparse.c @@ -0,0 +1,2200 @@ +/* A Bison parser, made by GNU Bison 3.7.5. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output, and Bison version. */ +#define YYBISON 30705 + +/* Bison version string. */ +#define YYBISON_VERSION "3.7.5" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 1 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + +/* Substitute the variable and function names. */ +#define yyparse expr_yyparse +#define yylex expr_yylex +#define yyerror expr_yyerror +#define yydebug expr_yydebug +#define yynerrs expr_yynerrs + +/* First part of user prologue. */ +#line 1 "exprparse.y" + +/*------------------------------------------------------------------------- + * + * exprparse.y + * bison grammar for a simple expression syntax + * + * Portions Copyright (c) 1996-2022, 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); + + +#line 116 "exprparse.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast<Type> (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + + +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int expr_yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + NULL_CONST = 258, /* NULL_CONST */ + INTEGER_CONST = 259, /* INTEGER_CONST */ + MAXINT_PLUS_ONE_CONST = 260, /* MAXINT_PLUS_ONE_CONST */ + DOUBLE_CONST = 261, /* DOUBLE_CONST */ + BOOLEAN_CONST = 262, /* BOOLEAN_CONST */ + VARIABLE = 263, /* VARIABLE */ + FUNCTION = 264, /* FUNCTION */ + AND_OP = 265, /* AND_OP */ + OR_OP = 266, /* OR_OP */ + NOT_OP = 267, /* NOT_OP */ + NE_OP = 268, /* NE_OP */ + LE_OP = 269, /* LE_OP */ + GE_OP = 270, /* GE_OP */ + LS_OP = 271, /* LS_OP */ + RS_OP = 272, /* RS_OP */ + IS_OP = 273, /* IS_OP */ + CASE_KW = 274, /* CASE_KW */ + WHEN_KW = 275, /* WHEN_KW */ + THEN_KW = 276, /* THEN_KW */ + ELSE_KW = 277, /* ELSE_KW */ + END_KW = 278, /* END_KW */ + ISNULL_OP = 279, /* ISNULL_OP */ + NOTNULL_OP = 280, /* NOTNULL_OP */ + UNARY = 281 /* UNARY */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 49 "exprparse.y" + + int64 ival; + double dval; + bool bval; + char *str; + PgBenchExpr *expr; + PgBenchExprList *elist; + +#line 198 "exprparse.c" + +}; +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + + +int expr_yyparse (yyscan_t yyscanner); + + +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_NULL_CONST = 3, /* NULL_CONST */ + YYSYMBOL_INTEGER_CONST = 4, /* INTEGER_CONST */ + YYSYMBOL_MAXINT_PLUS_ONE_CONST = 5, /* MAXINT_PLUS_ONE_CONST */ + YYSYMBOL_DOUBLE_CONST = 6, /* DOUBLE_CONST */ + YYSYMBOL_BOOLEAN_CONST = 7, /* BOOLEAN_CONST */ + YYSYMBOL_VARIABLE = 8, /* VARIABLE */ + YYSYMBOL_FUNCTION = 9, /* FUNCTION */ + YYSYMBOL_AND_OP = 10, /* AND_OP */ + YYSYMBOL_OR_OP = 11, /* OR_OP */ + YYSYMBOL_NOT_OP = 12, /* NOT_OP */ + YYSYMBOL_NE_OP = 13, /* NE_OP */ + YYSYMBOL_LE_OP = 14, /* LE_OP */ + YYSYMBOL_GE_OP = 15, /* GE_OP */ + YYSYMBOL_LS_OP = 16, /* LS_OP */ + YYSYMBOL_RS_OP = 17, /* RS_OP */ + YYSYMBOL_IS_OP = 18, /* IS_OP */ + YYSYMBOL_CASE_KW = 19, /* CASE_KW */ + YYSYMBOL_WHEN_KW = 20, /* WHEN_KW */ + YYSYMBOL_THEN_KW = 21, /* THEN_KW */ + YYSYMBOL_ELSE_KW = 22, /* ELSE_KW */ + YYSYMBOL_END_KW = 23, /* END_KW */ + YYSYMBOL_ISNULL_OP = 24, /* ISNULL_OP */ + YYSYMBOL_NOTNULL_OP = 25, /* NOTNULL_OP */ + YYSYMBOL_26_ = 26, /* '<' */ + YYSYMBOL_27_ = 27, /* '>' */ + YYSYMBOL_28_ = 28, /* '=' */ + YYSYMBOL_29_ = 29, /* '|' */ + YYSYMBOL_30_ = 30, /* '#' */ + YYSYMBOL_31_ = 31, /* '&' */ + YYSYMBOL_32_ = 32, /* '~' */ + YYSYMBOL_33_ = 33, /* '+' */ + YYSYMBOL_34_ = 34, /* '-' */ + YYSYMBOL_35_ = 35, /* '*' */ + YYSYMBOL_36_ = 36, /* '/' */ + YYSYMBOL_37_ = 37, /* '%' */ + YYSYMBOL_UNARY = 38, /* UNARY */ + YYSYMBOL_39_ = 39, /* ',' */ + YYSYMBOL_40_ = 40, /* '(' */ + YYSYMBOL_41_ = 41, /* ')' */ + YYSYMBOL_YYACCEPT = 42, /* $accept */ + YYSYMBOL_result = 43, /* result */ + YYSYMBOL_elist = 44, /* elist */ + YYSYMBOL_expr = 45, /* expr */ + YYSYMBOL_when_then_list = 46, /* when_then_list */ + YYSYMBOL_case_control = 47, /* case_control */ + YYSYMBOL_function = 48 /* function */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + <limits.h> and (if available) <stdint.h> are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include <limits.h> /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include <stdint.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif +#endif + +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; +#else +typedef short yytype_int16; +#endif + +/* Work around bug in HP-UX 11.23, which defines these macros + incorrectly for preprocessor constants. This workaround can likely + be removed in 2023, as HPE has promised support for HP-UX 11.23 + (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of + <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */ +#ifdef __hpux +# undef UINT_LEAST8_MAX +# undef UINT_LEAST16_MAX +# define UINT_LEAST8_MAX 255 +# define UINT_LEAST16_MAX 65535 +#endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif +#endif + +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_int8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YY_USE(E) ((void) (E)) +#else +# define YY_USE(E) /* empty */ +#endif + +#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) + +#if !defined yyoverflow + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include <alloca.h> /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include <malloc.h> /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* !defined yyoverflow */ + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 25 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 320 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 42 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 7 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 47 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 88 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 281 + + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex. */ +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 30, 2, 37, 31, 2, + 40, 41, 35, 33, 39, 34, 2, 36, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 26, 28, 27, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 29, 2, 32, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 38 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 83, 83, 88, 89, 90, 93, 94, 96, 99, + 102, 104, 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, + 124, 125, 129, 130, 135, 139, 145, 146, 147, 148, + 150, 151, 152, 156, 157, 160, 161, 163 +}; +#endif + +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if YYDEBUG || 0 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "\"invalid token\"", "NULL_CONST", + "INTEGER_CONST", "MAXINT_PLUS_ONE_CONST", "DOUBLE_CONST", + "BOOLEAN_CONST", "VARIABLE", "FUNCTION", "AND_OP", "OR_OP", "NOT_OP", + "NE_OP", "LE_OP", "GE_OP", "LS_OP", "RS_OP", "IS_OP", "CASE_KW", + "WHEN_KW", "THEN_KW", "ELSE_KW", "END_KW", "ISNULL_OP", "NOTNULL_OP", + "'<'", "'>'", "'='", "'|'", "'#'", "'&'", "'~'", "'+'", "'-'", "'*'", + "'/'", "'%'", "UNARY", "','", "'('", "')'", "$accept", "result", "elist", + "expr", "when_then_list", "case_control", "function", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_int16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 60, 62, 61, 124, + 35, 38, 126, 43, 45, 42, 47, 37, 281, 44, + 40, 41 +}; +#endif + +#define YYPACT_NINF (-33) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-1) + +#define yytable_value_is_error(Yyn) \ + ((Yyn) == YYTABLE_NINF) + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + 64, -33, -33, -33, -33, -33, -33, 64, -19, 64, + 64, 46, 64, 13, 205, -33, -22, 258, 64, -6, + 11, -33, -33, -33, 92, -33, 64, 64, 64, 64, + 64, 64, 64, 3, -33, -33, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 121, 64, + 64, -33, -33, 258, 233, 283, 283, 283, 11, 11, + -33, -33, 5, 283, 283, 283, 11, 11, 11, -9, + -9, -33, -33, -33, -32, 205, 64, 149, 177, -33, + -33, 64, -33, 205, 64, -33, 205, 205 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_int8 yydefact[] = +{ + 0, 36, 38, 39, 37, 40, 47, 0, 0, 0, + 0, 0, 0, 0, 2, 42, 0, 11, 0, 0, + 10, 7, 9, 8, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 30, 31, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, + 0, 45, 6, 28, 29, 22, 18, 20, 26, 27, + 32, 34, 0, 17, 19, 21, 24, 25, 23, 12, + 13, 14, 15, 16, 0, 4, 0, 0, 0, 33, + 35, 0, 41, 44, 0, 46, 5, 43 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -33, -33, -33, -7, -33, -33, -33 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + 0, 13, 74, 14, 19, 15, 16 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int8 yytable[] = +{ + 17, 18, 20, 21, 23, 24, 60, 81, 79, 82, + 61, 48, 80, 25, 49, 62, 50, 51, 47, 53, + 54, 55, 56, 57, 58, 59, 44, 45, 46, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, + 75, 0, 77, 78, 42, 43, 44, 45, 46, 1, + 2, 22, 3, 4, 5, 6, 0, 0, 7, 0, + 0, 0, 0, 0, 0, 8, 0, 1, 2, 83, + 3, 4, 5, 6, 86, 0, 7, 87, 9, 10, + 11, 0, 0, 8, 0, 0, 12, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 9, 10, 11, 0, + 0, 0, 26, 27, 12, 28, 29, 30, 31, 32, + 33, 0, 0, 0, 0, 0, 34, 35, 36, 37, + 38, 39, 40, 41, 0, 42, 43, 44, 45, 46, + 0, 26, 27, 52, 28, 29, 30, 31, 32, 33, + 0, 0, 76, 0, 0, 34, 35, 36, 37, 38, + 39, 40, 41, 0, 42, 43, 44, 45, 46, 26, + 27, 0, 28, 29, 30, 31, 32, 33, 0, 0, + 84, 0, 0, 34, 35, 36, 37, 38, 39, 40, + 41, 0, 42, 43, 44, 45, 46, 26, 27, 0, + 28, 29, 30, 31, 32, 33, 0, 0, 0, 0, + 85, 34, 35, 36, 37, 38, 39, 40, 41, 0, + 42, 43, 44, 45, 46, 26, 27, 0, 28, 29, + 30, 31, 32, 33, 0, 0, 0, 0, 0, 34, + 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, + 44, 45, 46, 26, 0, 0, 28, 29, 30, 31, + 32, 33, 0, 0, 0, 0, 0, 34, 35, 36, + 37, 38, 39, 40, 41, 0, 42, 43, 44, 45, + 46, 28, 29, 30, 31, 32, 33, 0, 0, 0, + 0, 0, 34, 35, 36, 37, 38, 39, 40, 41, + 0, 42, 43, 44, 45, 46, -1, -1, -1, 31, + 32, 0, 0, 0, 0, 0, 0, 0, 0, -1, + -1, -1, 39, 40, 41, 0, 42, 43, 44, 45, + 46 +}; + +static const yytype_int8 yycheck[] = +{ + 7, 20, 9, 10, 11, 12, 3, 39, 3, 41, + 7, 18, 7, 0, 20, 12, 22, 23, 40, 26, + 27, 28, 29, 30, 31, 32, 35, 36, 37, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, -1, 49, 50, 33, 34, 35, 36, 37, 3, + 4, 5, 6, 7, 8, 9, -1, -1, 12, -1, + -1, -1, -1, -1, -1, 19, -1, 3, 4, 76, + 6, 7, 8, 9, 81, -1, 12, 84, 32, 33, + 34, -1, -1, 19, -1, -1, 40, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 32, 33, 34, -1, + -1, -1, 10, 11, 40, 13, 14, 15, 16, 17, + 18, -1, -1, -1, -1, -1, 24, 25, 26, 27, + 28, 29, 30, 31, -1, 33, 34, 35, 36, 37, + -1, 10, 11, 41, 13, 14, 15, 16, 17, 18, + -1, -1, 21, -1, -1, 24, 25, 26, 27, 28, + 29, 30, 31, -1, 33, 34, 35, 36, 37, 10, + 11, -1, 13, 14, 15, 16, 17, 18, -1, -1, + 21, -1, -1, 24, 25, 26, 27, 28, 29, 30, + 31, -1, 33, 34, 35, 36, 37, 10, 11, -1, + 13, 14, 15, 16, 17, 18, -1, -1, -1, -1, + 23, 24, 25, 26, 27, 28, 29, 30, 31, -1, + 33, 34, 35, 36, 37, 10, 11, -1, 13, 14, + 15, 16, 17, 18, -1, -1, -1, -1, -1, 24, + 25, 26, 27, 28, 29, 30, 31, -1, 33, 34, + 35, 36, 37, 10, -1, -1, 13, 14, 15, 16, + 17, 18, -1, -1, -1, -1, -1, 24, 25, 26, + 27, 28, 29, 30, 31, -1, 33, 34, 35, 36, + 37, 13, 14, 15, 16, 17, 18, -1, -1, -1, + -1, -1, 24, 25, 26, 27, 28, 29, 30, 31, + -1, 33, 34, 35, 36, 37, 13, 14, 15, 16, + 17, -1, -1, -1, -1, -1, -1, -1, -1, 26, + 27, 28, 29, 30, 31, -1, 33, 34, 35, 36, + 37 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 3, 4, 6, 7, 8, 9, 12, 19, 32, + 33, 34, 40, 43, 45, 47, 48, 45, 20, 46, + 45, 45, 5, 45, 45, 0, 10, 11, 13, 14, + 15, 16, 17, 18, 24, 25, 26, 27, 28, 29, + 30, 31, 33, 34, 35, 36, 37, 40, 45, 20, + 22, 23, 41, 45, 45, 45, 45, 45, 45, 45, + 3, 7, 12, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 44, 45, 21, 45, 45, 3, + 7, 39, 41, 45, 21, 23, 45, 45 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_int8 yyr1[] = +{ + 0, 42, 43, 44, 44, 44, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 46, 46, 47, 47, 48 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 1, 0, 1, 3, 3, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 3, 4, 3, 4, 1, 1, 1, 1, + 1, 4, 1, 5, 4, 3, 5, 1 +}; + + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (yyscanner, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use YYerror or YYUNDEF. */ +#define YYERRCODE YYUNDEF + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +/* This macro is provided for backward compatibility. */ +# ifndef YY_LOCATION_PRINT +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value, yyscanner); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, yyscan_t yyscanner) +{ + FILE *yyoutput = yyo; + YY_USE (yyoutput); + YY_USE (yyscanner); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yykind < YYNTOKENS) + YYPRINT (yyo, yytoknum[yykind], *yyvaluep); +# endif + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ + +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, yyscan_t yyscanner) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep, yyscanner); + YYFPRINTF (yyo, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule, yyscan_t yyscanner) +{ + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)], yyscanner); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, yyscanner); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + + + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep, yyscan_t yyscanner) +{ + YY_USE (yyvaluep); + YY_USE (yyscanner); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (yyscan_t yyscanner) +{ +/* Lookahead token kind. */ +int yychar; + + +/* The semantic value of the lookahead symbol. */ +/* Default value used for initialization, for pacifying older GCCs + or non-GCC compilers. */ +YY_INITIAL_VALUE (static YYSTYPE yyval_default;) +YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); + + /* Number of syntax errors so far. */ + int yynerrs = 0; + + yy_state_fast_t yystate = 0; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus = 0; + + /* Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* Their size. */ + YYPTRDIFF_T yystacksize = YYINITDEPTH; + + /* The state stack: array, bottom, top. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss = yyssa; + yy_state_t *yyssp = yyss; + + /* The semantic value stack: array, bottom, top. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp = yyvs; + + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead symbol kind. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yychar = YYEMPTY; /* Cause a token to be read. */ + goto yysetstate; + + +/*------------------------------------------------------------. +| yynewstate -- push a new state, which is found in yystate. | +`------------------------------------------------------------*/ +yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + goto yyexhaustedlab; +#else + { + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; + +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (&yylval, yyscanner); + } + + if (yychar <= YYEOF) + { + yychar = YYEOF; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else if (yychar == YYerror) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = YYUNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + /* Discard the shifted token. */ + yychar = YYEMPTY; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: /* result: expr */ +#line 83 "exprparse.y" + { + expr_parse_result = (yyvsp[0].expr); + (void) yynerrs; /* suppress compiler warning */ + } +#line 1339 "exprparse.c" + break; + + case 3: /* elist: %empty */ +#line 88 "exprparse.y" + { (yyval.elist) = NULL; } +#line 1345 "exprparse.c" + break; + + case 4: /* elist: expr */ +#line 89 "exprparse.y" + { (yyval.elist) = make_elist((yyvsp[0].expr), NULL); } +#line 1351 "exprparse.c" + break; + + case 5: /* elist: elist ',' expr */ +#line 90 "exprparse.y" + { (yyval.elist) = make_elist((yyvsp[0].expr), (yyvsp[-2].elist)); } +#line 1357 "exprparse.c" + break; + + case 6: /* expr: '(' expr ')' */ +#line 93 "exprparse.y" + { (yyval.expr) = (yyvsp[-1].expr); } +#line 1363 "exprparse.c" + break; + + case 7: /* expr: '+' expr */ +#line 94 "exprparse.y" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1369 "exprparse.c" + break; + + case 8: /* expr: '-' expr */ +#line 96 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "-", + make_integer_constant(0), (yyvsp[0].expr)); } +#line 1376 "exprparse.c" + break; + + case 9: /* expr: '-' MAXINT_PLUS_ONE_CONST */ +#line 100 "exprparse.y" + { (yyval.expr) = make_integer_constant(PG_INT64_MIN); } +#line 1382 "exprparse.c" + break; + + case 10: /* expr: '~' expr */ +#line 102 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "#", + make_integer_constant(~INT64CONST(0)), (yyvsp[0].expr)); } +#line 1389 "exprparse.c" + break; + + case 11: /* expr: NOT_OP expr */ +#line 104 "exprparse.y" + { (yyval.expr) = make_uop(yyscanner, "!not", (yyvsp[0].expr)); } +#line 1395 "exprparse.c" + break; + + case 12: /* expr: expr '+' expr */ +#line 105 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "+", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1401 "exprparse.c" + break; + + case 13: /* expr: expr '-' expr */ +#line 106 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "-", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1407 "exprparse.c" + break; + + case 14: /* expr: expr '*' expr */ +#line 107 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "*", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1413 "exprparse.c" + break; + + case 15: /* expr: expr '/' expr */ +#line 108 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "/", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1419 "exprparse.c" + break; + + case 16: /* expr: expr '%' expr */ +#line 109 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "mod", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1425 "exprparse.c" + break; + + case 17: /* expr: expr '<' expr */ +#line 110 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "<", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1431 "exprparse.c" + break; + + case 18: /* expr: expr LE_OP expr */ +#line 111 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "<=", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1437 "exprparse.c" + break; + + case 19: /* expr: expr '>' expr */ +#line 112 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "<", (yyvsp[0].expr), (yyvsp[-2].expr)); } +#line 1443 "exprparse.c" + break; + + case 20: /* expr: expr GE_OP expr */ +#line 113 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "<=", (yyvsp[0].expr), (yyvsp[-2].expr)); } +#line 1449 "exprparse.c" + break; + + case 21: /* expr: expr '=' expr */ +#line 114 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "=", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1455 "exprparse.c" + break; + + case 22: /* expr: expr NE_OP expr */ +#line 115 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "<>", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1461 "exprparse.c" + break; + + case 23: /* expr: expr '&' expr */ +#line 116 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "&", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1467 "exprparse.c" + break; + + case 24: /* expr: expr '|' expr */ +#line 117 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "|", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1473 "exprparse.c" + break; + + case 25: /* expr: expr '#' expr */ +#line 118 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "#", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1479 "exprparse.c" + break; + + case 26: /* expr: expr LS_OP expr */ +#line 119 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "<<", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1485 "exprparse.c" + break; + + case 27: /* expr: expr RS_OP expr */ +#line 120 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, ">>", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1491 "exprparse.c" + break; + + case 28: /* expr: expr AND_OP expr */ +#line 121 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "!and", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1497 "exprparse.c" + break; + + case 29: /* expr: expr OR_OP expr */ +#line 122 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "!or", (yyvsp[-2].expr), (yyvsp[0].expr)); } +#line 1503 "exprparse.c" + break; + + case 30: /* expr: expr ISNULL_OP */ +#line 124 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "!is", (yyvsp[-1].expr), make_null_constant()); } +#line 1509 "exprparse.c" + break; + + case 31: /* expr: expr NOTNULL_OP */ +#line 125 "exprparse.y" + { + (yyval.expr) = make_uop(yyscanner, "!not", + make_op(yyscanner, "!is", (yyvsp[-1].expr), make_null_constant())); + } +#line 1518 "exprparse.c" + break; + + case 32: /* expr: expr IS_OP NULL_CONST */ +#line 129 "exprparse.y" + { (yyval.expr) = make_op(yyscanner, "!is", (yyvsp[-2].expr), make_null_constant()); } +#line 1524 "exprparse.c" + break; + + case 33: /* expr: expr IS_OP NOT_OP NULL_CONST */ +#line 131 "exprparse.y" + { + (yyval.expr) = make_uop(yyscanner, "!not", + make_op(yyscanner, "!is", (yyvsp[-3].expr), make_null_constant())); + } +#line 1533 "exprparse.c" + break; + + case 34: /* expr: expr IS_OP BOOLEAN_CONST */ +#line 136 "exprparse.y" + { + (yyval.expr) = make_op(yyscanner, "!is", (yyvsp[-2].expr), make_boolean_constant((yyvsp[0].bval))); + } +#line 1541 "exprparse.c" + break; + + case 35: /* expr: expr IS_OP NOT_OP BOOLEAN_CONST */ +#line 140 "exprparse.y" + { + (yyval.expr) = make_uop(yyscanner, "!not", + make_op(yyscanner, "!is", (yyvsp[-3].expr), make_boolean_constant((yyvsp[0].bval)))); + } +#line 1550 "exprparse.c" + break; + + case 36: /* expr: NULL_CONST */ +#line 145 "exprparse.y" + { (yyval.expr) = make_null_constant(); } +#line 1556 "exprparse.c" + break; + + case 37: /* expr: BOOLEAN_CONST */ +#line 146 "exprparse.y" + { (yyval.expr) = make_boolean_constant((yyvsp[0].bval)); } +#line 1562 "exprparse.c" + break; + + case 38: /* expr: INTEGER_CONST */ +#line 147 "exprparse.y" + { (yyval.expr) = make_integer_constant((yyvsp[0].ival)); } +#line 1568 "exprparse.c" + break; + + case 39: /* expr: DOUBLE_CONST */ +#line 148 "exprparse.y" + { (yyval.expr) = make_double_constant((yyvsp[0].dval)); } +#line 1574 "exprparse.c" + break; + + case 40: /* expr: VARIABLE */ +#line 150 "exprparse.y" + { (yyval.expr) = make_variable((yyvsp[0].str)); } +#line 1580 "exprparse.c" + break; + + case 41: /* expr: function '(' elist ')' */ +#line 151 "exprparse.y" + { (yyval.expr) = make_func(yyscanner, (yyvsp[-3].ival), (yyvsp[-1].elist)); } +#line 1586 "exprparse.c" + break; + + case 42: /* expr: case_control */ +#line 152 "exprparse.y" + { (yyval.expr) = (yyvsp[0].expr); } +#line 1592 "exprparse.c" + break; + + case 43: /* when_then_list: when_then_list WHEN_KW expr THEN_KW expr */ +#line 156 "exprparse.y" + { (yyval.elist) = make_elist((yyvsp[0].expr), make_elist((yyvsp[-2].expr), (yyvsp[-4].elist))); } +#line 1598 "exprparse.c" + break; + + case 44: /* when_then_list: WHEN_KW expr THEN_KW expr */ +#line 157 "exprparse.y" + { (yyval.elist) = make_elist((yyvsp[0].expr), make_elist((yyvsp[-2].expr), NULL)); } +#line 1604 "exprparse.c" + break; + + case 45: /* case_control: CASE_KW when_then_list END_KW */ +#line 160 "exprparse.y" + { (yyval.expr) = make_case(yyscanner, (yyvsp[-1].elist), make_null_constant()); } +#line 1610 "exprparse.c" + break; + + case 46: /* case_control: CASE_KW when_then_list ELSE_KW expr END_KW */ +#line 161 "exprparse.y" + { (yyval.expr) = make_case(yyscanner, (yyvsp[-3].elist), (yyvsp[-1].expr)); } +#line 1616 "exprparse.c" + break; + + case 47: /* function: FUNCTION */ +#line 163 "exprparse.y" + { (yyval.ival) = find_func(yyscanner, (yyvsp[0].str)); pg_free((yyvsp[0].str)); } +#line 1622 "exprparse.c" + break; + + +#line 1626 "exprparse.c" + + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + yyerror (yyscanner, YY_("syntax error")); + } + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, yyscanner); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + /* Pop stack until we find a state that shifts the error token. */ + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp, yyscanner); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + + +#if !defined yyoverflow +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (yyscanner, YY_("memory exhausted")); + yyresult = 2; + goto yyreturn; +#endif + + +/*-------------------------------------------------------. +| yyreturn -- parsing is finished, clean up and return. | +`-------------------------------------------------------*/ +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, yyscanner); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, yyscanner); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 166 "exprparse.y" + + +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" diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y new file mode 100644 index 0000000..18da3c6 --- /dev/null +++ b/src/bin/pgbench/exprparse.y @@ -0,0 +1,546 @@ +%{ +/*------------------------------------------------------------------------- + * + * exprparse.y + * bison grammar for a simple expression syntax + * + * Portions Copyright (c) 1996-2022, 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; + (void) yynerrs; /* suppress compiler warning */ + } + +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" diff --git a/src/bin/pgbench/exprscan.c b/src/bin/pgbench/exprscan.c new file mode 100644 index 0000000..d69bca0 --- /dev/null +++ b/src/bin/pgbench/exprscan.c @@ -0,0 +1,2835 @@ +#line 2 "exprscan.c" + +#line 4 "exprscan.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define expr_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer expr_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define expr_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer expr_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define expr_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer expr_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define expr_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string expr_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define expr_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes expr_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define expr_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer expr_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define expr_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer expr_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define expr_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state expr_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define expr_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer expr_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define expr_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state expr_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define expr_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state expr_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define expr_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack expr_yyensure_buffer_stack +#endif + +#ifdef yylex +#define expr_yylex_ALREADY_DEFINED +#else +#define yylex expr_yylex +#endif + +#ifdef yyrestart +#define expr_yyrestart_ALREADY_DEFINED +#else +#define yyrestart expr_yyrestart +#endif + +#ifdef yylex_init +#define expr_yylex_init_ALREADY_DEFINED +#else +#define yylex_init expr_yylex_init +#endif + +#ifdef yylex_init_extra +#define expr_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra expr_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define expr_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy expr_yylex_destroy +#endif + +#ifdef yyget_debug +#define expr_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug expr_yyget_debug +#endif + +#ifdef yyset_debug +#define expr_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug expr_yyset_debug +#endif + +#ifdef yyget_extra +#define expr_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra expr_yyget_extra +#endif + +#ifdef yyset_extra +#define expr_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra expr_yyset_extra +#endif + +#ifdef yyget_in +#define expr_yyget_in_ALREADY_DEFINED +#else +#define yyget_in expr_yyget_in +#endif + +#ifdef yyset_in +#define expr_yyset_in_ALREADY_DEFINED +#else +#define yyset_in expr_yyset_in +#endif + +#ifdef yyget_out +#define expr_yyget_out_ALREADY_DEFINED +#else +#define yyget_out expr_yyget_out +#endif + +#ifdef yyset_out +#define expr_yyset_out_ALREADY_DEFINED +#else +#define yyset_out expr_yyset_out +#endif + +#ifdef yyget_leng +#define expr_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng expr_yyget_leng +#endif + +#ifdef yyget_text +#define expr_yyget_text_ALREADY_DEFINED +#else +#define yyget_text expr_yyget_text +#endif + +#ifdef yyget_lineno +#define expr_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno expr_yyget_lineno +#endif + +#ifdef yyset_lineno +#define expr_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno expr_yyset_lineno +#endif + +#ifdef yyget_column +#define expr_yyget_column_ALREADY_DEFINED +#else +#define yyget_column expr_yyget_column +#endif + +#ifdef yyset_column +#define expr_yyset_column_ALREADY_DEFINED +#else +#define yyset_column expr_yyset_column +#endif + +#ifdef yywrap +#define expr_yywrap_ALREADY_DEFINED +#else +#define yywrap expr_yywrap +#endif + +#ifdef yyget_lval +#define expr_yyget_lval_ALREADY_DEFINED +#else +#define yyget_lval expr_yyget_lval +#endif + +#ifdef yyset_lval +#define expr_yyset_lval_ALREADY_DEFINED +#else +#define yyset_lval expr_yyset_lval +#endif + +#ifdef yyalloc +#define expr_yyalloc_ALREADY_DEFINED +#else +#define yyalloc expr_yyalloc +#endif + +#ifdef yyrealloc +#define expr_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc expr_yyrealloc +#endif + +#ifdef yyfree +#define expr_yyfree_ALREADY_DEFINED +#else +#define yyfree expr_yyfree +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* An opaque pointer. */ +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +/* For convenience, these vars (plus the bison vars far below) + are macros in the reentrant scanner. */ +#define yyin yyg->yyin_r +#define yyout yyg->yyout_r +#define yyextra yyg->yyextra_r +#define yyleng yyg->yyleng_r +#define yytext yyg->yytext_r +#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno) +#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column) +#define yy_flex_debug yyg->yy_flex_debug_r + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN yyg->yy_start = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START ((yyg->yy_start - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin , yyscanner ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = yyg->yy_hold_char; \ + YY_RESTORE_YY_MORE_OFFSET \ + yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \ + ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] + +void yyrestart ( FILE *input_file , yyscan_t yyscanner ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size , yyscan_t yyscanner ); +void yy_delete_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yy_flush_buffer ( YY_BUFFER_STATE b , yyscan_t yyscanner ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer , yyscan_t yyscanner ); +void yypop_buffer_state ( yyscan_t yyscanner ); + +static void yyensure_buffer_stack ( yyscan_t yyscanner ); +static void yy_load_buffer_state ( yyscan_t yyscanner ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER , yyscanner) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); + +void *yyalloc ( yy_size_t , yyscan_t yyscanner ); +void *yyrealloc ( void *, yy_size_t , yyscan_t yyscanner ); +void yyfree ( void * , yyscan_t yyscanner ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (yyscanner); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define expr_yywrap(yyscanner) (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +typedef int yy_state_type; + +#define yytext_ptr yytext_r + +static yy_state_type yy_get_previous_state ( yyscan_t yyscanner ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state , yyscan_t yyscanner); +static int yy_get_next_buffer ( yyscan_t yyscanner ); +static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + yyg->yytext_ptr = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + yyg->yy_hold_char = *yy_cp; \ + *yy_cp = '\0'; \ + yyg->yy_c_buf_p = yy_cp; +#define YY_NUM_RULES 51 +#define YY_END_OF_BUFFER 52 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[129] = + { 0, + 0, 0, 0, 0, 52, 1, 3, 5, 1, 50, + 47, 49, 50, 22, 10, 21, 24, 25, 8, 6, + 26, 7, 50, 9, 43, 43, 50, 18, 11, 19, + 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, + 50, 20, 23, 1, 1, 3, 4, 0, 47, 13, + 45, 44, 43, 0, 43, 38, 16, 14, 12, 15, + 17, 46, 46, 46, 46, 46, 46, 30, 46, 46, + 28, 46, 46, 46, 48, 0, 2, 0, 0, 44, + 0, 44, 43, 27, 46, 46, 37, 46, 46, 29, + 46, 46, 46, 46, 0, 45, 43, 33, 36, 46, + + 46, 46, 39, 35, 40, 34, 43, 41, 46, 46, + 43, 31, 46, 43, 32, 43, 43, 43, 43, 43, + 43, 43, 43, 43, 43, 43, 42, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 2, 2, 4, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 5, 1, 6, 1, 7, 8, 1, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 1, 28, + 29, 30, 1, 1, 31, 32, 33, 34, 35, 36, + 32, 37, 38, 32, 32, 39, 32, 40, 41, 32, + 32, 42, 43, 44, 45, 32, 46, 32, 32, 32, + 1, 47, 1, 1, 32, 1, 31, 32, 33, 34, + + 35, 36, 32, 37, 38, 32, 32, 39, 32, 40, + 41, 32, 32, 42, 43, 44, 45, 32, 46, 32, + 32, 32, 1, 48, 1, 49, 1, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32 + } ; + +static const YY_CHAR yy_meta[50] = + { 0, + 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 3, 1, 3, 4, 1, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, + 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 1, 1, 1 + } ; + +static const flex_int16_t yy_base[138] = + { 0, + 0, 3, 50, 0, 204, 156, 6, 205, 97, 205, + 7, 205, 173, 205, 205, 205, 205, 205, 205, 205, + 205, 205, 0, 205, 0, 1, 0, 0, 205, 2, + 161, 0, 169, 0, 168, 155, 0, 155, 0, 159, + 9, 205, 205, 148, 99, 15, 205, 191, 19, 205, + 158, 157, 172, 10, 171, 0, 205, 205, 205, 205, + 205, 0, 155, 145, 144, 152, 146, 144, 139, 143, + 0, 146, 135, 143, 205, 172, 205, 169, 13, 128, + 32, 0, 14, 0, 120, 110, 0, 100, 97, 101, + 101, 99, 103, 97, 92, 0, 29, 0, 0, 101, + + 95, 88, 0, 0, 0, 0, 19, 0, 93, 92, + 103, 0, 74, 88, 0, 89, 91, 92, 93, 97, + 100, 101, 104, 102, 111, 110, 7, 205, 146, 153, + 9, 157, 160, 163, 166, 171, 174 + } ; + +static const flex_int16_t yy_def[138] = + { 0, + 129, 129, 128, 3, 128, 130, 128, 128, 130, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 131, 128, 132, 132, 133, 128, 128, 128, + 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, + 128, 128, 128, 130, 130, 128, 128, 128, 128, 128, + 131, 135, 26, 136, 26, 133, 128, 128, 128, 128, + 128, 134, 134, 134, 134, 134, 134, 134, 134, 134, + 134, 134, 134, 134, 128, 128, 128, 128, 137, 135, + 136, 81, 26, 134, 134, 134, 134, 134, 134, 134, + 134, 134, 134, 134, 137, 95, 26, 134, 134, 134, + + 134, 134, 134, 134, 134, 134, 26, 134, 134, 134, + 26, 134, 134, 26, 134, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 0, 128, 128, + 128, 128, 128, 128, 128, 128, 128 + } ; + +static const flex_int16_t yy_nxt[255] = + { 0, + 128, 7, 8, 7, 7, 8, 7, 46, 49, 46, + 49, 75, 76, 51, 52, 52, 46, 128, 46, 55, + 49, 81, 49, 81, 95, 53, 95, 57, 58, 59, + 60, 61, 53, 97, 54, 54, 72, 53, 65, 66, + 69, 73, 111, 128, 70, 128, 9, 53, 107, 9, + 10, 11, 12, 11, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 32, 34, 35, 32, 36, 32, 37, + 38, 32, 32, 39, 32, 40, 41, 42, 43, 47, + + 48, 77, 78, 128, 116, 128, 53, 53, 117, 53, + 53, 53, 115, 118, 120, 53, 119, 121, 53, 53, + 53, 114, 53, 122, 123, 124, 125, 126, 53, 53, + 113, 112, 110, 109, 127, 108, 106, 105, 104, 103, + 102, 101, 100, 45, 99, 45, 6, 6, 6, 6, + 6, 6, 6, 44, 98, 44, 44, 44, 44, 44, + 53, 53, 54, 53, 56, 56, 56, 62, 62, 62, + 80, 77, 80, 82, 75, 82, 96, 94, 96, 93, + 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, + 53, 54, 79, 47, 45, 74, 71, 68, 67, 64, + + 63, 50, 45, 128, 5, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128 + } ; + +static const flex_int16_t yy_chk[255] = + { 0, + 0, 1, 1, 1, 2, 2, 2, 7, 11, 7, + 11, 41, 41, 131, 25, 26, 46, 0, 46, 26, + 49, 54, 49, 54, 79, 127, 79, 28, 28, 28, + 30, 30, 83, 83, 25, 26, 39, 107, 34, 34, + 37, 39, 107, 81, 37, 81, 1, 97, 97, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 9, + + 9, 45, 45, 95, 114, 95, 114, 116, 116, 117, + 118, 119, 113, 117, 119, 120, 118, 120, 121, 122, + 124, 111, 123, 121, 122, 123, 124, 125, 126, 125, + 110, 109, 102, 101, 126, 100, 94, 93, 92, 91, + 90, 89, 88, 9, 86, 45, 129, 129, 129, 129, + 129, 129, 129, 130, 85, 130, 130, 130, 130, 130, + 132, 132, 80, 132, 133, 133, 133, 134, 134, 134, + 135, 78, 135, 136, 76, 136, 137, 74, 137, 73, + 72, 70, 69, 68, 67, 66, 65, 64, 63, 55, + 53, 52, 51, 48, 44, 40, 38, 36, 35, 33, + + 31, 13, 6, 5, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +#line 1 "exprscan.l" +#line 2 "exprscan.l" +/*------------------------------------------------------------------------- + * + * exprscan.l + * lexical scanner for pgbench backslash commands + * + * This lexer supports two operating modes: + * + * In INITIAL state, just parse off whitespace-separated words (this mode + * is basically equivalent to strtok(), which is what we used to use). + * + * In EXPR state, lex for the simple expression syntax of exprparse.y. + * + * In either mode, stop upon hitting newline or end of string. + * + * Note that this lexer operates within the framework created by psqlscan.l, + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/bin/pgbench/exprscan.l + * + *------------------------------------------------------------------------- + */ + +#include "fe_utils/psqlscan_int.h" + +/* context information for reporting errors in expressions */ +static const char *expr_source = NULL; +static int expr_lineno = 0; +static int expr_start_offset = 0; +static const char *expr_command = NULL; + +/* indicates whether last yylex() call read a newline */ +static bool last_was_newline = false; + +/* + * Work around a bug in flex 2.5.35: it emits a couple of functions that + * it forgets to emit declarations for. Since we use -Wmissing-prototypes, + * this would cause warnings. Providing our own declarations should be + * harmless even when the bug gets fixed. + */ +extern int expr_yyget_column(yyscan_t yyscanner); +extern void expr_yyset_column(int column_no, yyscan_t yyscanner); + +/* LCOV_EXCL_START */ + +#line 790 "exprscan.c" +/* Except for the prefix, these options should match psqlscan.l */ +#define YY_NO_INPUT 1 +/* Character classes */ +/* {space} + {nonspace} + {newline} should cover all characters */ +/* Line continuation marker */ +/* case insensitive keywords */ +/* Exclusive states */ + +#line 799 "exprscan.c" + +#define INITIAL 0 +#define EXPR 1 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include <unistd.h> +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Holds the entire state of the reentrant scanner. */ +struct yyguts_t + { + + /* User-defined. Not touched by flex. */ + YY_EXTRA_TYPE yyextra_r; + + /* The rest are the same as the globals declared in the non-reentrant scanner. */ + FILE *yyin_r, *yyout_r; + size_t yy_buffer_stack_top; /**< index of top of stack. */ + size_t yy_buffer_stack_max; /**< capacity of stack. */ + YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ + char yy_hold_char; + int yy_n_chars; + int yyleng_r; + char *yy_c_buf_p; + int yy_init; + int yy_start; + int yy_did_buffer_switch_on_eof; + int yy_start_stack_ptr; + int yy_start_stack_depth; + int *yy_start_stack; + yy_state_type yy_last_accepting_state; + char* yy_last_accepting_cpos; + + int yylineno_r; + int yy_flex_debug_r; + + char *yytext_r; + int yy_more_flag; + int yy_more_len; + + YYSTYPE * yylval_r; + + }; /* end struct yyguts_t */ + +static int yy_init_globals ( yyscan_t yyscanner ); + + /* This must go here because YYSTYPE and YYLTYPE are included + * from bison output in section 1.*/ + # define yylval yyg->yylval_r + +int yylex_init (yyscan_t* scanner); + +int yylex_init_extra ( YY_EXTRA_TYPE user_defined, yyscan_t* scanner); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( yyscan_t yyscanner ); + +int yyget_debug ( yyscan_t yyscanner ); + +void yyset_debug ( int debug_flag , yyscan_t yyscanner ); + +YY_EXTRA_TYPE yyget_extra ( yyscan_t yyscanner ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined , yyscan_t yyscanner ); + +FILE *yyget_in ( yyscan_t yyscanner ); + +void yyset_in ( FILE * _in_str , yyscan_t yyscanner ); + +FILE *yyget_out ( yyscan_t yyscanner ); + +void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); + + int yyget_leng ( yyscan_t yyscanner ); + +char *yyget_text ( yyscan_t yyscanner ); + +int yyget_lineno ( yyscan_t yyscanner ); + +void yyset_lineno ( int _line_number , yyscan_t yyscanner ); + +int yyget_column ( yyscan_t yyscanner ); + +void yyset_column ( int _column_no , yyscan_t yyscanner ); + +YYSTYPE * yyget_lval ( yyscan_t yyscanner ); + +void yyset_lval ( YYSTYPE * yylval_param , yyscan_t yyscanner ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( yyscan_t yyscanner ); +#else +extern int yywrap ( yyscan_t yyscanner ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int , yyscan_t yyscanner); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * , yyscan_t yyscanner); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( yyscan_t yyscanner ); +#else +static int input ( yyscan_t yyscanner ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner); + +#define YY_DECL int yylex \ + (YYSTYPE * yylval_param , yyscan_t yyscanner) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yylval = yylval_param; + + if ( !yyg->yy_init ) + { + yyg->yy_init = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! yyg->yy_start ) + yyg->yy_start = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_load_buffer_state( yyscanner ); + } + + { +#line 93 "exprscan.l" + + + +#line 97 "exprscan.l" + /* Declare some local variables inside yylex(), for convenience */ + PsqlScanState cur_state = yyextra; + + /* + * Force flex into the state indicated by start_state. This has a + * couple of purposes: it lets some of the functions below set a new + * starting state without ugly direct access to flex variables, and it + * allows us to transition from one flex lexer to another so that we + * can lex different parts of the source string using separate lexers. + */ + BEGIN(cur_state->start_state); + + /* Reset was-newline flag */ + last_was_newline = false; + + + /* INITIAL state */ + +#line 1095 "exprscan.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = yyg->yy_c_buf_p; + + /* Support of yytext. */ + *yy_cp = yyg->yy_hold_char; + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = yyg->yy_start; +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 129 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 128 ); + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = yyg->yy_hold_char; + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 115 "exprscan.l" +{ + /* Found a word, emit and return it */ + psqlscan_emit(cur_state, yytext, yyleng); + return 1; + } + YY_BREAK +/* + * We need this rule to avoid returning "word\" instead of recognizing + * a continuation marker just after a word: + */ +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +#line 125 "exprscan.l" +{ + /* Found "word\\\r?\n", emit and return just "word" */ + int wordlen = yyleng - 2; + if (yytext[wordlen] == '\r') + wordlen--; + Assert(yytext[wordlen] == '\\'); + psqlscan_emit(cur_state, yytext, wordlen); + return 1; + } + YY_BREAK +case 3: +YY_RULE_SETUP +#line 135 "exprscan.l" +{ /* ignore */ } + YY_BREAK +case 4: +/* rule 4 can match eol */ +YY_RULE_SETUP +#line 137 "exprscan.l" +{ /* ignore */ } + YY_BREAK +case 5: +/* rule 5 can match eol */ +YY_RULE_SETUP +#line 139 "exprscan.l" +{ + /* report end of command */ + last_was_newline = true; + return 0; + } + YY_BREAK +/* EXPR state */ + +case 6: +YY_RULE_SETUP +#line 149 "exprscan.l" +{ return '+'; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 150 "exprscan.l" +{ return '-'; } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 151 "exprscan.l" +{ return '*'; } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 152 "exprscan.l" +{ return '/'; } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 153 "exprscan.l" +{ return '%'; } /* C version, also in Pg SQL */ + YY_BREAK +case 11: +YY_RULE_SETUP +#line 154 "exprscan.l" +{ return '='; } + YY_BREAK +case 12: +YY_RULE_SETUP +#line 155 "exprscan.l" +{ return NE_OP; } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 156 "exprscan.l" +{ return NE_OP; } /* C version, also in Pg SQL */ + YY_BREAK +case 14: +YY_RULE_SETUP +#line 157 "exprscan.l" +{ return LE_OP; } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 158 "exprscan.l" +{ return GE_OP; } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 159 "exprscan.l" +{ return LS_OP; } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 160 "exprscan.l" +{ return RS_OP; } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 161 "exprscan.l" +{ return '<'; } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 162 "exprscan.l" +{ return '>'; } + YY_BREAK +case 20: +YY_RULE_SETUP +#line 163 "exprscan.l" +{ return '|'; } + YY_BREAK +case 21: +YY_RULE_SETUP +#line 164 "exprscan.l" +{ return '&'; } + YY_BREAK +case 22: +YY_RULE_SETUP +#line 165 "exprscan.l" +{ return '#'; } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 166 "exprscan.l" +{ return '~'; } + YY_BREAK +case 24: +YY_RULE_SETUP +#line 168 "exprscan.l" +{ return '('; } + YY_BREAK +case 25: +YY_RULE_SETUP +#line 169 "exprscan.l" +{ return ')'; } + YY_BREAK +case 26: +YY_RULE_SETUP +#line 170 "exprscan.l" +{ return ','; } + YY_BREAK +case 27: +YY_RULE_SETUP +#line 172 "exprscan.l" +{ return AND_OP; } + YY_BREAK +case 28: +YY_RULE_SETUP +#line 173 "exprscan.l" +{ return OR_OP; } + YY_BREAK +case 29: +YY_RULE_SETUP +#line 174 "exprscan.l" +{ return NOT_OP; } + YY_BREAK +case 30: +YY_RULE_SETUP +#line 175 "exprscan.l" +{ return IS_OP; } + YY_BREAK +case 31: +YY_RULE_SETUP +#line 176 "exprscan.l" +{ return ISNULL_OP; } + YY_BREAK +case 32: +YY_RULE_SETUP +#line 177 "exprscan.l" +{ return NOTNULL_OP; } + YY_BREAK +case 33: +YY_RULE_SETUP +#line 179 "exprscan.l" +{ return CASE_KW; } + YY_BREAK +case 34: +YY_RULE_SETUP +#line 180 "exprscan.l" +{ return WHEN_KW; } + YY_BREAK +case 35: +YY_RULE_SETUP +#line 181 "exprscan.l" +{ return THEN_KW; } + YY_BREAK +case 36: +YY_RULE_SETUP +#line 182 "exprscan.l" +{ return ELSE_KW; } + YY_BREAK +case 37: +YY_RULE_SETUP +#line 183 "exprscan.l" +{ return END_KW; } + YY_BREAK +case 38: +YY_RULE_SETUP +#line 185 "exprscan.l" +{ + yylval->str = pg_strdup(yytext + 1); + return VARIABLE; + } + YY_BREAK +case 39: +YY_RULE_SETUP +#line 190 "exprscan.l" +{ return NULL_CONST; } + YY_BREAK +case 40: +YY_RULE_SETUP +#line 191 "exprscan.l" +{ + yylval->bval = true; + return BOOLEAN_CONST; + } + YY_BREAK +case 41: +YY_RULE_SETUP +#line 195 "exprscan.l" +{ + yylval->bval = false; + return BOOLEAN_CONST; + } + YY_BREAK +case 42: +YY_RULE_SETUP +#line 199 "exprscan.l" +{ + /* + * Special handling for PG_INT64_MIN, which can't + * accurately be represented here, as the minus sign is + * lexed separately and INT64_MIN can't be represented as + * a positive integer. + */ + return MAXINT_PLUS_ONE_CONST; + } + YY_BREAK +case 43: +YY_RULE_SETUP +#line 208 "exprscan.l" +{ + if (!strtoint64(yytext, true, &yylval->ival)) + expr_yyerror_more(yyscanner, "bigint constant overflow", + strdup(yytext)); + return INTEGER_CONST; + } + YY_BREAK +case 44: +YY_RULE_SETUP +#line 214 "exprscan.l" +{ + if (!strtodouble(yytext, true, &yylval->dval)) + expr_yyerror_more(yyscanner, "double constant overflow", + strdup(yytext)); + return DOUBLE_CONST; + } + YY_BREAK +case 45: +YY_RULE_SETUP +#line 220 "exprscan.l" +{ + if (!strtodouble(yytext, true, &yylval->dval)) + expr_yyerror_more(yyscanner, "double constant overflow", + strdup(yytext)); + return DOUBLE_CONST; + } + YY_BREAK +case 46: +YY_RULE_SETUP +#line 226 "exprscan.l" +{ + yylval->str = pg_strdup(yytext); + return FUNCTION; + } + YY_BREAK +case 47: +YY_RULE_SETUP +#line 231 "exprscan.l" +{ /* ignore */ } + YY_BREAK +case 48: +/* rule 48 can match eol */ +YY_RULE_SETUP +#line 233 "exprscan.l" +{ /* ignore */ } + YY_BREAK +case 49: +/* rule 49 can match eol */ +YY_RULE_SETUP +#line 235 "exprscan.l" +{ + /* report end of command */ + last_was_newline = true; + return 0; + } + YY_BREAK +case 50: +YY_RULE_SETUP +#line 241 "exprscan.l" +{ + /* + * must strdup yytext so that expr_yyerror_more doesn't + * change it while finding end of line + */ + expr_yyerror_more(yyscanner, "unexpected character", + pg_strdup(yytext)); + /* NOTREACHED, syntax_error calls exit() */ + return 0; + } + YY_BREAK + +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(EXPR): +#line 254 "exprscan.l" +{ + if (cur_state->buffer_stack == NULL) + return 0; /* end of input reached */ + + /* + * We were expanding a variable, so pop the inclusion + * stack and keep lexing + */ + psqlscan_pop_buffer_stack(cur_state); + psqlscan_select_top_buffer(cur_state); + } + YY_BREAK +case 51: +YY_RULE_SETUP +#line 266 "exprscan.l" +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK +#line 1494 "exprscan.c" + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = yyg->yy_hold_char; + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner); + + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++yyg->yy_c_buf_p; + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = yyg->yy_last_accepting_cpos; + yy_current_state = yyg->yy_last_accepting_state; + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_END_OF_FILE: + { + yyg->yy_did_buffer_switch_on_eof = 0; + + if ( yywrap( yyscanner ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = + yyg->yytext_ptr + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + yyg->yy_c_buf_p = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars]; + + yy_current_state = yy_get_previous_state( yyscanner ); + + yy_cp = yyg->yy_c_buf_p; + yy_bp = yyg->yytext_ptr + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = yyg->yytext_ptr; + int number_to_move, i; + int ret_val; + + if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) (yyg->yy_c_buf_p - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + yyg->yy_n_chars, num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + if ( yyg->yy_n_chars == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin , yyscanner); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + yyg->yy_n_chars += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR; + + yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (yyscan_t yyscanner) +{ + yy_state_type yy_current_state; + char *yy_cp; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_current_state = yyg->yy_start; + + for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 129 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state , yyscan_t yyscanner) +{ + int yy_is_jam; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */ + char *yy_cp = yyg->yy_c_buf_p; + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + yyg->yy_last_accepting_state = yy_current_state; + yyg->yy_last_accepting_cpos = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 129 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 128); + + (void)yyg; + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (yyscan_t yyscanner) +#else + static int input (yyscan_t yyscanner) +#endif + +{ + int c; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + *yyg->yy_c_buf_p = yyg->yy_hold_char; + + if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] ) + /* This was really a NUL. */ + *yyg->yy_c_buf_p = '\0'; + + else + { /* need more input */ + int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); + ++yyg->yy_c_buf_p; + + switch ( yy_get_next_buffer( yyscanner ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin , yyscanner); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( yyscanner ) ) + return 0; + + if ( ! yyg->yy_did_buffer_switch_on_eof ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(yyscanner); +#else + return input(yyscanner); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + yyg->yy_c_buf_p = yyg->yytext_ptr + offset; + break; + } + } + } + + c = *(unsigned char *) yyg->yy_c_buf_p; /* cast for 8-bit char's */ + *yyg->yy_c_buf_p = '\0'; /* preserve yytext */ + yyg->yy_hold_char = *++yyg->yy_c_buf_p; + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * @param yyscanner The scanner object. + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (yyscanner); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE , yyscanner); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file , yyscanner); + yy_load_buffer_state( yyscanner ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * @param yyscanner The scanner object. + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (yyscanner); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( yyscanner ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + yyg->yy_did_buffer_switch_on_eof = 1; +} + +static void yy_load_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + yyg->yy_hold_char = *yyg->yy_c_buf_p; +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * @param yyscanner The scanner object. + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) , yyscanner ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file , yyscanner); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * @param yyscanner The scanner object. + */ + void yy_delete_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf , yyscanner ); + + yyfree( (void *) b , yyscanner ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file , yyscan_t yyscanner) + +{ + int oerrno = errno; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + yy_flush_buffer( b , yyscanner); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * @param yyscanner The scanner object. + */ + void yy_flush_buffer (YY_BUFFER_STATE b , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( yyscanner ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * @param yyscanner The scanner object. + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(yyscanner); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *yyg->yy_c_buf_p = yyg->yy_hold_char; + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p; + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars; + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + yyg->yy_buffer_stack_top++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * @param yyscanner The scanner object. + */ +void yypop_buffer_state (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER , yyscanner); + YY_CURRENT_BUFFER_LVALUE = NULL; + if (yyg->yy_buffer_stack_top > 0) + --yyg->yy_buffer_stack_top; + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( yyscanner ); + yyg->yy_did_buffer_switch_on_eof = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (yyscan_t yyscanner) +{ + yy_size_t num_to_alloc; + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (!yyg->yy_buffer_stack) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + yyg->yy_buffer_stack_max = num_to_alloc; + yyg->yy_buffer_stack_top = 0; + return; + } + + if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = yyg->yy_buffer_stack_max + grow_size; + yyg->yy_buffer_stack = (struct yy_buffer_state**)yyrealloc + (yyg->yy_buffer_stack, + num_to_alloc * sizeof(struct yy_buffer_state*) + , yyscanner); + if ( ! yyg->yy_buffer_stack ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*)); + yyg->yy_buffer_stack_max = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) , yyscanner ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b , yyscanner ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) , yyscanner); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * @param yyscanner The scanner object. + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n , yyscanner ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n , yyscanner); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = yyg->yy_hold_char; \ + yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ + yyg->yy_hold_char = *yyg->yy_c_buf_p; \ + *yyg->yy_c_buf_p = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the user-defined data for this scanner. + * @param yyscanner The scanner object. + */ +YY_EXTRA_TYPE yyget_extra (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyextra; +} + +/** Get the current line number. + * @param yyscanner The scanner object. + */ +int yyget_lineno (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yylineno; +} + +/** Get the current column number. + * @param yyscanner The scanner object. + */ +int yyget_column (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + if (! YY_CURRENT_BUFFER) + return 0; + + return yycolumn; +} + +/** Get the input stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_in (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyin; +} + +/** Get the output stream. + * @param yyscanner The scanner object. + */ +FILE *yyget_out (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyout; +} + +/** Get the length of the current token. + * @param yyscanner The scanner object. + */ +int yyget_leng (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yyleng; +} + +/** Get the current token. + * @param yyscanner The scanner object. + */ + +char *yyget_text (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yytext; +} + +/** Set the user-defined data. This data is never touched by the scanner. + * @param user_defined The data to be associated with this scanner. + * @param yyscanner The scanner object. + */ +void yyset_extra (YY_EXTRA_TYPE user_defined , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyextra = user_defined ; +} + +/** Set the current line number. + * @param _line_number line number + * @param yyscanner The scanner object. + */ +void yyset_lineno (int _line_number , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* lineno is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_lineno called with no buffer" ); + + yylineno = _line_number; +} + +/** Set the current column. + * @param _column_no column number + * @param yyscanner The scanner object. + */ +void yyset_column (int _column_no , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* column is only valid if an input buffer exists. */ + if (! YY_CURRENT_BUFFER ) + YY_FATAL_ERROR( "yyset_column called with no buffer" ); + + yycolumn = _column_no; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * @param yyscanner The scanner object. + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yyout = _out_str ; +} + +int yyget_debug (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yy_flex_debug; +} + +void yyset_debug (int _bdebug , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yy_flex_debug = _bdebug ; +} + +/* Accessor methods for yylval and yylloc */ + +YYSTYPE * yyget_lval (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + return yylval; +} + +void yyset_lval (YYSTYPE * yylval_param , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + yylval = yylval_param; +} + +/* User-visible API */ + +/* yylex_init is special because it creates the scanner itself, so it is + * the ONLY reentrant function that doesn't take the scanner as the last argument. + * That's why we explicitly handle the declaration, instead of using our macros. + */ +int yylex_init(yyscan_t* ptr_yy_globals) +{ + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), NULL ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + return yy_init_globals ( *ptr_yy_globals ); +} + +/* yylex_init_extra has the same functionality as yylex_init, but follows the + * convention of taking the scanner as the last argument. Note however, that + * this is a *pointer* to a scanner, as it will be allocated by this call (and + * is the reason, too, why this function also must handle its own declaration). + * The user defined value in the first argument will be available to yyalloc in + * the yyextra field. + */ +int yylex_init_extra( YY_EXTRA_TYPE yy_user_defined, yyscan_t* ptr_yy_globals ) +{ + struct yyguts_t dummy_yyguts; + + yyset_extra (yy_user_defined, &dummy_yyguts); + + if (ptr_yy_globals == NULL){ + errno = EINVAL; + return 1; + } + + *ptr_yy_globals = (yyscan_t) yyalloc ( sizeof( struct yyguts_t ), &dummy_yyguts ); + + if (*ptr_yy_globals == NULL){ + errno = ENOMEM; + return 1; + } + + /* By setting to 0xAA, we expose bugs in + yy_init_globals. Leave at 0x00 for releases. */ + memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t)); + + yyset_extra (yy_user_defined, *ptr_yy_globals); + + return yy_init_globals ( *ptr_yy_globals ); +} + +static int yy_init_globals (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + yyg->yy_buffer_stack = NULL; + yyg->yy_buffer_stack_top = 0; + yyg->yy_buffer_stack_max = 0; + yyg->yy_c_buf_p = NULL; + yyg->yy_init = 0; + yyg->yy_start = 0; + + yyg->yy_start_stack_ptr = 0; + yyg->yy_start_stack_depth = 0; + yyg->yy_start_stack = NULL; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER , yyscanner ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(yyscanner); + } + + /* Destroy the stack itself. */ + yyfree(yyg->yy_buffer_stack , yyscanner); + yyg->yy_buffer_stack = NULL; + + /* Destroy the start condition stack. */ + yyfree( yyg->yy_start_stack , yyscanner ); + yyg->yy_start_stack = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( yyscanner); + + /* Destroy the main struct (reentrant only). */ + yyfree ( yyscanner , yyscanner ); + yyscanner = NULL; + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s , yyscan_t yyscanner) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr , yyscan_t yyscanner) +{ + struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; + (void)yyg; + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#line 266 "exprscan.l" + + +/* LCOV_EXCL_STOP */ + +void +expr_yyerror_more(yyscan_t yyscanner, const char *message, const char *more) +{ + PsqlScanState state = yyget_extra(yyscanner); + int error_detection_offset = expr_scanner_offset(state) - 1; + YYSTYPE lval; + char *full_line; + + /* + * While parsing an expression, we may not have collected the whole line + * yet from the input source. Lex till EOL so we can report whole line. + * (If we're at EOF, it's okay to call yylex() an extra time.) + */ + if (!last_was_newline) + { + while (yylex(&lval, yyscanner)) + /* skip */ ; + } + + /* Extract the line, trimming trailing newline if any */ + full_line = expr_scanner_get_substring(state, + expr_start_offset, + expr_scanner_offset(state), + true); + + syntax_error(expr_source, expr_lineno, full_line, expr_command, + message, more, error_detection_offset - expr_start_offset); +} + +void +expr_yyerror(yyscan_t yyscanner, const char *message) +{ + expr_yyerror_more(yyscanner, message, NULL); +} + +/* + * Collect a space-separated word from a backslash command and return it + * in word_buf, along with its starting string offset in *offset. + * Returns true if successful, false if at end of command. + */ +bool +expr_lex_one_word(PsqlScanState state, PQExpBuffer word_buf, int *offset) +{ + int lexresult; + YYSTYPE lval; + + /* Must be scanning already */ + Assert(state->scanbufhandle != NULL); + + /* Set current output target */ + state->output_buf = word_buf; + resetPQExpBuffer(word_buf); + + /* Set input source */ + if (state->buffer_stack != NULL) + yy_switch_to_buffer(state->buffer_stack->buf, state->scanner); + else + yy_switch_to_buffer(state->scanbufhandle, state->scanner); + + /* Set start state */ + state->start_state = INITIAL; + + /* And lex. */ + lexresult = yylex(&lval, state->scanner); + + /* + * Save start offset of word, if any. We could do this more efficiently, + * but for now this seems fine. + */ + if (lexresult) + *offset = expr_scanner_offset(state) - word_buf->len; + else + *offset = -1; + + /* + * In case the caller returns to using the regular SQL lexer, reselect the + * appropriate initial state. + */ + psql_scan_reselect_sql_lexer(state); + + return (bool) lexresult; +} + +/* + * Prepare to lex an expression via expr_yyparse(). + * + * Returns the yyscan_t that is to be passed to expr_yyparse(). + * (This is just state->scanner, but callers don't need to know that.) + */ +yyscan_t +expr_scanner_init(PsqlScanState state, + const char *source, int lineno, int start_offset, + const char *command) +{ + /* Save error context info */ + expr_source = source; + expr_lineno = lineno; + expr_start_offset = start_offset; + expr_command = command; + + /* Must be scanning already */ + Assert(state->scanbufhandle != NULL); + + /* Set current output target */ + state->output_buf = NULL; + + /* Set input source */ + if (state->buffer_stack != NULL) + yy_switch_to_buffer(state->buffer_stack->buf, state->scanner); + else + yy_switch_to_buffer(state->scanbufhandle, state->scanner); + + /* Set start state */ + state->start_state = EXPR; + + return state->scanner; +} + +/* + * Finish lexing an expression. + */ +void +expr_scanner_finish(yyscan_t yyscanner) +{ + PsqlScanState state = yyget_extra(yyscanner); + + /* + * Reselect appropriate initial state for SQL lexer. + */ + psql_scan_reselect_sql_lexer(state); +} + +/* + * Get offset from start of string to end of current lexer token. + * + * We rely on the knowledge that flex modifies the scan buffer by storing + * a NUL at the end of the current token (yytext). Note that this might + * not work quite right if we were parsing a sub-buffer, but since pgbench + * never invokes that functionality, it doesn't matter. + */ +int +expr_scanner_offset(PsqlScanState state) +{ + return strlen(state->scanbuf); +} + +/* + * Get a malloc'd copy of the lexer input string from start_offset + * to just before end_offset. If chomp is true, drop any trailing + * newline(s). + */ +char * +expr_scanner_get_substring(PsqlScanState state, + int start_offset, int end_offset, + bool chomp) +{ + char *result; + const char *scanptr = state->scanbuf + start_offset; + int slen = end_offset - start_offset; + + Assert(slen >= 0); + Assert(end_offset <= strlen(state->scanbuf)); + + if (chomp) + { + while (slen > 0 && + (scanptr[slen - 1] == '\n' || scanptr[slen - 1] == '\r')) + slen--; + } + + result = (char *) pg_malloc(slen + 1); + memcpy(result, scanptr, slen); + result[slen] = '\0'; + + return result; +} + +/* + * Get the line number associated with the given string offset + * (which must not be past the end of where we've lexed to). + */ +int +expr_scanner_get_lineno(PsqlScanState state, int offset) +{ + int lineno = 1; + const char *p = state->scanbuf; + + while (*p && offset > 0) + { + if (*p == '\n') + lineno++; + p++, offset--; + } + return lineno; +} + diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l new file mode 100644 index 0000000..4f63818 --- /dev/null +++ b/src/bin/pgbench/exprscan.l @@ -0,0 +1,463 @@ +%{ +/*------------------------------------------------------------------------- + * + * exprscan.l + * lexical scanner for pgbench backslash commands + * + * This lexer supports two operating modes: + * + * In INITIAL state, just parse off whitespace-separated words (this mode + * is basically equivalent to strtok(), which is what we used to use). + * + * In EXPR state, lex for the simple expression syntax of exprparse.y. + * + * In either mode, stop upon hitting newline or end of string. + * + * Note that this lexer operates within the framework created by psqlscan.l, + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/bin/pgbench/exprscan.l + * + *------------------------------------------------------------------------- + */ + +#include "fe_utils/psqlscan_int.h" + +/* context information for reporting errors in expressions */ +static const char *expr_source = NULL; +static int expr_lineno = 0; +static int expr_start_offset = 0; +static const char *expr_command = NULL; + +/* indicates whether last yylex() call read a newline */ +static bool last_was_newline = false; + +/* + * Work around a bug in flex 2.5.35: it emits a couple of functions that + * it forgets to emit declarations for. Since we use -Wmissing-prototypes, + * this would cause warnings. Providing our own declarations should be + * harmless even when the bug gets fixed. + */ +extern int expr_yyget_column(yyscan_t yyscanner); +extern void expr_yyset_column(int column_no, yyscan_t yyscanner); + +/* LCOV_EXCL_START */ + +%} + +/* Except for the prefix, these options should match psqlscan.l */ +%option reentrant +%option bison-bridge +%option 8bit +%option never-interactive +%option nodefault +%option noinput +%option nounput +%option noyywrap +%option warn +%option prefix="expr_yy" + +/* Character classes */ +alpha [a-zA-Z\200-\377_] +digit [0-9] +alnum [A-Za-z\200-\377_0-9] +/* {space} + {nonspace} + {newline} should cover all characters */ +space [ \t\r\f\v] +nonspace [^ \t\r\f\v\n] +newline [\n] + +/* Line continuation marker */ +continuation \\\r?{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 + +%% + +%{ + /* Declare some local variables inside yylex(), for convenience */ + PsqlScanState cur_state = yyextra; + + /* + * Force flex into the state indicated by start_state. This has a + * couple of purposes: it lets some of the functions below set a new + * starting state without ugly direct access to flex variables, and it + * allows us to transition from one flex lexer to another so that we + * can lex different parts of the source string using separate lexers. + */ + BEGIN(cur_state->start_state); + + /* Reset was-newline flag */ + last_was_newline = false; +%} + + /* INITIAL state */ + +{nonspace}+ { + /* Found a word, emit and return it */ + psqlscan_emit(cur_state, yytext, yyleng); + return 1; + } + + /* + * We need this rule to avoid returning "word\" instead of recognizing + * a continuation marker just after a word: + */ +{nonspace}+{continuation} { + /* Found "word\\\r?\n", emit and return just "word" */ + int wordlen = yyleng - 2; + if (yytext[wordlen] == '\r') + wordlen--; + Assert(yytext[wordlen] == '\\'); + psqlscan_emit(cur_state, yytext, wordlen); + return 1; + } + +{space}+ { /* ignore */ } + +{continuation} { /* ignore */ } + +{newline} { + /* report end of command */ + last_was_newline = true; + return 0; + } + + /* EXPR state */ + +<EXPR>{ + +"+" { 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; + } +"9223372036854775808" { + /* + * Special handling for PG_INT64_MIN, which can't + * accurately be represented here, as the minus sign is + * lexed separately and INT64_MIN can't be represented as + * a positive integer. + */ + return MAXINT_PLUS_ONE_CONST; + } +{digit}+ { + if (!strtoint64(yytext, true, &yylval->ival)) + expr_yyerror_more(yyscanner, "bigint constant overflow", + strdup(yytext)); + return INTEGER_CONST; + } +{digit}+(\.{digit}*)?([eE][-+]?{digit}+)? { + if (!strtodouble(yytext, true, &yylval->dval)) + expr_yyerror_more(yyscanner, "double constant overflow", + strdup(yytext)); + return DOUBLE_CONST; + } +\.{digit}+([eE][-+]?{digit}+)? { + if (!strtodouble(yytext, true, &yylval->dval)) + expr_yyerror_more(yyscanner, "double constant overflow", + strdup(yytext)); + return DOUBLE_CONST; + } +{alpha}{alnum}* { + yylval->str = pg_strdup(yytext); + return FUNCTION; + } + +{space}+ { /* ignore */ } + +{continuation} { /* ignore */ } + +{newline} { + /* report end of command */ + last_was_newline = true; + return 0; + } + +. { + /* + * must strdup yytext so that expr_yyerror_more doesn't + * change it while finding end of line + */ + expr_yyerror_more(yyscanner, "unexpected character", + pg_strdup(yytext)); + /* NOTREACHED, syntax_error calls exit() */ + return 0; + } + +} + +<<EOF>> { + if (cur_state->buffer_stack == NULL) + return 0; /* end of input reached */ + + /* + * We were expanding a variable, so pop the inclusion + * stack and keep lexing + */ + psqlscan_pop_buffer_stack(cur_state); + psqlscan_select_top_buffer(cur_state); + } + +%% + +/* LCOV_EXCL_STOP */ + +void +expr_yyerror_more(yyscan_t yyscanner, const char *message, const char *more) +{ + PsqlScanState state = yyget_extra(yyscanner); + int error_detection_offset = expr_scanner_offset(state) - 1; + YYSTYPE lval; + char *full_line; + + /* + * While parsing an expression, we may not have collected the whole line + * yet from the input source. Lex till EOL so we can report whole line. + * (If we're at EOF, it's okay to call yylex() an extra time.) + */ + if (!last_was_newline) + { + while (yylex(&lval, yyscanner)) + /* skip */ ; + } + + /* Extract the line, trimming trailing newline if any */ + full_line = expr_scanner_get_substring(state, + expr_start_offset, + expr_scanner_offset(state), + true); + + syntax_error(expr_source, expr_lineno, full_line, expr_command, + message, more, error_detection_offset - expr_start_offset); +} + +void +expr_yyerror(yyscan_t yyscanner, const char *message) +{ + expr_yyerror_more(yyscanner, message, NULL); +} + +/* + * Collect a space-separated word from a backslash command and return it + * in word_buf, along with its starting string offset in *offset. + * Returns true if successful, false if at end of command. + */ +bool +expr_lex_one_word(PsqlScanState state, PQExpBuffer word_buf, int *offset) +{ + int lexresult; + YYSTYPE lval; + + /* Must be scanning already */ + Assert(state->scanbufhandle != NULL); + + /* Set current output target */ + state->output_buf = word_buf; + resetPQExpBuffer(word_buf); + + /* Set input source */ + if (state->buffer_stack != NULL) + yy_switch_to_buffer(state->buffer_stack->buf, state->scanner); + else + yy_switch_to_buffer(state->scanbufhandle, state->scanner); + + /* Set start state */ + state->start_state = INITIAL; + + /* And lex. */ + lexresult = yylex(&lval, state->scanner); + + /* + * Save start offset of word, if any. We could do this more efficiently, + * but for now this seems fine. + */ + if (lexresult) + *offset = expr_scanner_offset(state) - word_buf->len; + else + *offset = -1; + + /* + * In case the caller returns to using the regular SQL lexer, reselect the + * appropriate initial state. + */ + psql_scan_reselect_sql_lexer(state); + + return (bool) lexresult; +} + +/* + * Prepare to lex an expression via expr_yyparse(). + * + * Returns the yyscan_t that is to be passed to expr_yyparse(). + * (This is just state->scanner, but callers don't need to know that.) + */ +yyscan_t +expr_scanner_init(PsqlScanState state, + const char *source, int lineno, int start_offset, + const char *command) +{ + /* Save error context info */ + expr_source = source; + expr_lineno = lineno; + expr_start_offset = start_offset; + expr_command = command; + + /* Must be scanning already */ + Assert(state->scanbufhandle != NULL); + + /* Set current output target */ + state->output_buf = NULL; + + /* Set input source */ + if (state->buffer_stack != NULL) + yy_switch_to_buffer(state->buffer_stack->buf, state->scanner); + else + yy_switch_to_buffer(state->scanbufhandle, state->scanner); + + /* Set start state */ + state->start_state = EXPR; + + return state->scanner; +} + +/* + * Finish lexing an expression. + */ +void +expr_scanner_finish(yyscan_t yyscanner) +{ + PsqlScanState state = yyget_extra(yyscanner); + + /* + * Reselect appropriate initial state for SQL lexer. + */ + psql_scan_reselect_sql_lexer(state); +} + +/* + * Get offset from start of string to end of current lexer token. + * + * We rely on the knowledge that flex modifies the scan buffer by storing + * a NUL at the end of the current token (yytext). Note that this might + * not work quite right if we were parsing a sub-buffer, but since pgbench + * never invokes that functionality, it doesn't matter. + */ +int +expr_scanner_offset(PsqlScanState state) +{ + return strlen(state->scanbuf); +} + +/* + * Get a malloc'd copy of the lexer input string from start_offset + * to just before end_offset. If chomp is true, drop any trailing + * newline(s). + */ +char * +expr_scanner_get_substring(PsqlScanState state, + int start_offset, int end_offset, + bool chomp) +{ + char *result; + const char *scanptr = state->scanbuf + start_offset; + int slen = end_offset - start_offset; + + Assert(slen >= 0); + Assert(end_offset <= strlen(state->scanbuf)); + + if (chomp) + { + while (slen > 0 && + (scanptr[slen - 1] == '\n' || scanptr[slen - 1] == '\r')) + slen--; + } + + result = (char *) pg_malloc(slen + 1); + memcpy(result, scanptr, slen); + result[slen] = '\0'; + + return result; +} + +/* + * Get the line number associated with the given string offset + * (which must not be past the end of where we've lexed to). + */ +int +expr_scanner_get_lineno(PsqlScanState state, int offset) +{ + int lineno = 1; + const char *p = state->scanbuf; + + while (*p && offset > 0) + { + if (*p == '\n') + lineno++; + p++, offset--; + } + return lineno; +} diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c new file mode 100644 index 0000000..e0cd754 --- /dev/null +++ b/src/bin/pgbench/pgbench.c @@ -0,0 +1,7854 @@ +/* + * pgbench.c + * + * A simple benchmark program for PostgreSQL + * Originally written by Tatsuo Ishii and enhanced by many contributors. + * + * src/bin/pgbench/pgbench.c + * Copyright (c) 2000-2022, PostgreSQL Global Development Group + * ALL RIGHTS RESERVED; + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written agreement + * is hereby granted, provided that the above copyright notice and this + * paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + */ + +#ifdef WIN32 +#define FD_SETSIZE 1024 /* must set before winsock2.h is included */ +#endif + +#include "postgres_fe.h" + +#include <ctype.h> +#include <float.h> +#include <limits.h> +#include <math.h> +#include <signal.h> +#include <time.h> +#include <sys/time.h> +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> /* for getrlimit */ +#endif + +/* For testing, PGBENCH_USE_SELECT can be defined to force use of that code */ +#if defined(HAVE_PPOLL) && !defined(PGBENCH_USE_SELECT) +#define POLL_USING_PPOLL +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#else /* no ppoll(), so use select() */ +#define POLL_USING_SELECT +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif + +#include "common/int.h" +#include "common/logging.h" +#include "common/pg_prng.h" +#include "common/string.h" +#include "common/username.h" +#include "fe_utils/cancel.h" +#include "fe_utils/conditional.h" +#include "fe_utils/option_utils.h" +#include "fe_utils/string_utils.h" +#include "getopt_long.h" +#include "libpq-fe.h" +#include "pgbench.h" +#include "port/pg_bitutils.h" +#include "portability/instr_time.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define ERRCODE_T_R_SERIALIZATION_FAILURE "40001" +#define ERRCODE_T_R_DEADLOCK_DETECTED "40P01" +#define ERRCODE_UNDEFINED_TABLE "42P01" + +/* + * Hashing constants + */ +#define FNV_PRIME UINT64CONST(0x100000001b3) +#define FNV_OFFSET_BASIS UINT64CONST(0xcbf29ce484222325) +#define MM2_MUL UINT64CONST(0xc6a4a7935bd1e995) +#define MM2_MUL_TIMES_8 UINT64CONST(0x35253c9ade8f4ca8) +#define MM2_ROT 47 + +/* + * Multi-platform socket set implementations + */ + +#ifdef POLL_USING_PPOLL +#define SOCKET_WAIT_METHOD "ppoll" + +typedef struct socket_set +{ + int maxfds; /* allocated length of pollfds[] array */ + int curfds; /* number currently in use */ + struct pollfd pollfds[FLEXIBLE_ARRAY_MEMBER]; +} socket_set; + +#endif /* POLL_USING_PPOLL */ + +#ifdef POLL_USING_SELECT +#define SOCKET_WAIT_METHOD "select" + +typedef struct socket_set +{ + int maxfd; /* largest FD currently set in fds */ + fd_set fds; +} socket_set; + +#endif /* POLL_USING_SELECT */ + +/* + * Multi-platform thread implementations + */ + +#ifdef WIN32 +/* Use Windows threads */ +#include <windows.h> +#define GETERRNO() (_dosmaperr(GetLastError()), errno) +#define THREAD_T HANDLE +#define THREAD_FUNC_RETURN_TYPE unsigned +#define THREAD_FUNC_RETURN return 0 +#define THREAD_FUNC_CC __stdcall +#define THREAD_CREATE(handle, function, arg) \ + ((*(handle) = (HANDLE) _beginthreadex(NULL, 0, (function), (arg), 0, NULL)) == 0 ? errno : 0) +#define THREAD_JOIN(handle) \ + (WaitForSingleObject(handle, INFINITE) != WAIT_OBJECT_0 ? \ + GETERRNO() : CloseHandle(handle) ? 0 : GETERRNO()) +#define THREAD_BARRIER_T SYNCHRONIZATION_BARRIER +#define THREAD_BARRIER_INIT(barrier, n) \ + (InitializeSynchronizationBarrier((barrier), (n), 0) ? 0 : GETERRNO()) +#define THREAD_BARRIER_WAIT(barrier) \ + EnterSynchronizationBarrier((barrier), \ + SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY) +#define THREAD_BARRIER_DESTROY(barrier) +#elif defined(ENABLE_THREAD_SAFETY) +/* Use POSIX threads */ +#include "port/pg_pthread.h" +#define THREAD_T pthread_t +#define THREAD_FUNC_RETURN_TYPE void * +#define THREAD_FUNC_RETURN return NULL +#define THREAD_FUNC_CC +#define THREAD_CREATE(handle, function, arg) \ + pthread_create((handle), NULL, (function), (arg)) +#define THREAD_JOIN(handle) \ + pthread_join((handle), NULL) +#define THREAD_BARRIER_T pthread_barrier_t +#define THREAD_BARRIER_INIT(barrier, n) \ + pthread_barrier_init((barrier), NULL, (n)) +#define THREAD_BARRIER_WAIT(barrier) pthread_barrier_wait((barrier)) +#define THREAD_BARRIER_DESTROY(barrier) pthread_barrier_destroy((barrier)) +#else +/* No threads implementation, use none (-j 1) */ +#define THREAD_T void * +#define THREAD_FUNC_RETURN_TYPE void * +#define THREAD_FUNC_RETURN return NULL +#define THREAD_FUNC_CC +#define THREAD_BARRIER_T int +#define THREAD_BARRIER_INIT(barrier, n) (*(barrier) = 0) +#define THREAD_BARRIER_WAIT(barrier) +#define THREAD_BARRIER_DESTROY(barrier) +#endif + + +/******************************************************************** + * some configurable parameters */ + +#define DEFAULT_INIT_STEPS "dtgvp" /* default -I setting */ +#define ALL_INIT_STEPS "dtgGvpf" /* all possible steps */ + +#define LOG_STEP_SECONDS 5 /* seconds between log messages */ +#define DEFAULT_NXACTS 10 /* default nxacts */ + +#define MIN_GAUSSIAN_PARAM 2.0 /* minimum parameter for gauss */ + +#define MIN_ZIPFIAN_PARAM 1.001 /* minimum parameter for zipfian */ +#define MAX_ZIPFIAN_PARAM 1000.0 /* maximum parameter for zipfian */ + +int nxacts = 0; /* number of transactions per client */ +int duration = 0; /* duration in seconds */ +int64 end_time = 0; /* when to stop in micro seconds, under -T */ + +/* + * scaling factor. for example, scale = 10 will make 1000000 tuples in + * pgbench_accounts table. + */ +int scale = 1; + +/* + * fillfactor. for example, fillfactor = 90 will use only 90 percent + * space during inserts and leave 10 percent free. + */ +int fillfactor = 100; + +/* + * use unlogged tables? + */ +bool unlogged_tables = false; + +/* + * log sampling rate (1.0 = log everything, 0.0 = option not given) + */ +double sample_rate = 0.0; + +/* + * When threads are throttled to a given rate limit, this is the target delay + * to reach that rate in usec. 0 is the default and means no throttling. + */ +double throttle_delay = 0; + +/* + * Transactions which take longer than this limit (in usec) are counted as + * late, and reported as such, although they are completed anyway. When + * throttling is enabled, execution time slots that are more than this late + * are skipped altogether, and counted separately. + */ +int64 latency_limit = 0; + +/* + * tablespace selection + */ +char *tablespace = NULL; +char *index_tablespace = NULL; + +/* + * Number of "pgbench_accounts" partitions. 0 is the default and means no + * partitioning. + */ +static int partitions = 0; + +/* partitioning strategy for "pgbench_accounts" */ +typedef enum +{ + PART_NONE, /* no partitioning */ + PART_RANGE, /* range partitioning */ + PART_HASH /* hash partitioning */ +} partition_method_t; + +static partition_method_t partition_method = PART_NONE; +static const char *PARTITION_METHOD[] = {"none", "range", "hash"}; + +/* random seed used to initialize base_random_sequence */ +int64 random_seed = -1; + +/* + * end of configurable parameters + *********************************************************************/ + +#define nbranches 1 /* Makes little sense to change this. Change + * -s instead */ +#define ntellers 10 +#define naccounts 100000 + +/* + * The scale factor at/beyond which 32bit integers are incapable of storing + * 64bit values. + * + * Although the actual threshold is 21474, we use 20000 because it is easier to + * document and remember, and isn't that far away from the real threshold. + */ +#define SCALE_32BIT_THRESHOLD 20000 + +bool use_log; /* log transaction latencies to a file */ +bool use_quiet; /* quiet logging onto stderr */ +int agg_interval; /* log aggregates instead of individual + * transactions */ +bool per_script_stats = false; /* whether to collect stats per script */ +int progress = 0; /* thread progress report every this seconds */ +bool progress_timestamp = false; /* progress report with Unix time */ +int nclients = 1; /* number of clients */ +int nthreads = 1; /* number of threads */ +bool is_connect; /* establish connection for each transaction */ +bool report_per_command = false; /* report per-command latencies, + * retries after errors and failures + * (errors without retrying) */ +int main_pid; /* main process id used in log filename */ + +/* + * There are different types of restrictions for deciding that the current + * transaction with a serialization/deadlock error can no longer be retried and + * should be reported as failed: + * - max_tries (--max-tries) can be used to limit the number of tries; + * - latency_limit (-L) can be used to limit the total time of tries; + * - duration (-T) can be used to limit the total benchmark time. + * + * They can be combined together, and you need to use at least one of them to + * retry the transactions with serialization/deadlock errors. If none of them is + * used, the default value of max_tries is 1 and such transactions will not be + * retried. + */ + +/* + * We cannot retry a transaction after the serialization/deadlock error if its + * number of tries reaches this maximum; if its value is zero, it is not used. + */ +uint32 max_tries = 1; + +bool failures_detailed = false; /* whether to group failures in + * reports or logs by basic types */ + +const char *pghost = NULL; +const char *pgport = NULL; +const char *username = NULL; +const char *dbName = NULL; +char *logfile_prefix = NULL; +const char *progname; + +#define WSEP '@' /* weight separator */ + +volatile bool timer_exceeded = false; /* flag from signal handler */ + +/* + * We don't want to allocate variables one by one; for efficiency, add a + * constant margin each time it overflows. + */ +#define VARIABLES_ALLOC_MARGIN 8 + +/* + * 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 *svalue; /* its value in string form, if known */ + PgBenchValue value; /* actual variable's value */ +} Variable; + +/* + * Data structure for client variables. + */ +typedef struct +{ + Variable *vars; /* array of variable definitions */ + int nvars; /* number of variables */ + + /* + * The maximum number of variables that we can currently store in 'vars' + * without having to reallocate more space. We must always have max_vars + * >= nvars. + */ + int max_vars; + + bool vars_sorted; /* are variables sorted by name? */ +} Variables; + +#define MAX_SCRIPTS 128 /* max number of SQL scripts allowed */ +#define SHELL_COMMAND_SIZE 256 /* maximum size allowed for shell command */ + +/* + * Simple data structure to keep stats about something. + * + * XXX probably the first value should be kept and used as an offset for + * better numerical stability... + */ +typedef struct SimpleStats +{ + int64 count; /* how many values were encountered */ + double min; /* the minimum seen */ + double max; /* the maximum seen */ + double sum; /* sum of values */ + double sum2; /* sum of squared values */ +} SimpleStats; + +/* + * The instr_time type is expensive when dealing with time arithmetic. Define + * a type to hold microseconds instead. Type int64 is good enough for about + * 584500 years. + */ +typedef int64 pg_time_usec_t; + +/* + * Data structure to hold various statistics: per-thread and per-script stats + * are maintained and merged together. + */ +typedef struct StatsData +{ + pg_time_usec_t start_time; /* interval start time, for aggregates */ + + /*---------- + * Transactions are counted depending on their execution and outcome. + * First a transaction may have started or not: skipped transactions occur + * under --rate and --latency-limit when the client is too late to execute + * them. Secondly, a started transaction may ultimately succeed or fail, + * possibly after some retries when --max-tries is not one. Thus + * + * the number of all transactions = + * 'skipped' (it was too late to execute them) + + * 'cnt' (the number of successful transactions) + + * 'failed' (the number of failed transactions). + * + * A successful transaction can have several unsuccessful tries before a + * successful run. Thus + * + * 'cnt' (the number of successful transactions) = + * successfully retried transactions (they got a serialization or a + * deadlock error(s), but were + * successfully retried from the very + * beginning) + + * directly successful transactions (they were successfully completed on + * the first try). + * + * A failed transaction is defined as unsuccessfully retried transactions. + * It can be one of two types: + * + * failed (the number of failed transactions) = + * 'serialization_failures' (they got a serialization error and were not + * successfully retried) + + * 'deadlock_failures' (they got a deadlock error and were not + * successfully retried). + * + * If the transaction was retried after a serialization or a deadlock + * error this does not guarantee that this retry was successful. Thus + * + * 'retries' (number of retries) = + * number of retries in all retried transactions = + * number of retries in (successfully retried transactions + + * failed transactions); + * + * 'retried' (number of all retried transactions) = + * successfully retried transactions + + * failed transactions. + *---------- + */ + int64 cnt; /* number of successful transactions, not + * including 'skipped' */ + int64 skipped; /* number of transactions skipped under --rate + * and --latency-limit */ + int64 retries; /* number of retries after a serialization or + * a deadlock error in all the transactions */ + int64 retried; /* number of all transactions that were + * retried after a serialization or a deadlock + * error (perhaps the last try was + * unsuccessful) */ + int64 serialization_failures; /* number of transactions that were + * not successfully retried after a + * serialization error */ + int64 deadlock_failures; /* number of transactions that were not + * successfully retried after a deadlock + * error */ + SimpleStats latency; + SimpleStats lag; +} StatsData; + +/* + * For displaying Unix epoch timestamps, as some time functions may have + * another reference. + */ +pg_time_usec_t epoch_shift; + +/* + * Error status for errors during script execution. + */ +typedef enum EStatus +{ + ESTATUS_NO_ERROR = 0, + ESTATUS_META_COMMAND_ERROR, + + /* SQL errors */ + ESTATUS_SERIALIZATION_ERROR, + ESTATUS_DEADLOCK_ERROR, + ESTATUS_OTHER_SQL_ERROR +} EStatus; + +/* + * Transaction status at the end of a command. + */ +typedef enum TStatus +{ + TSTATUS_IDLE, + TSTATUS_IN_BLOCK, + TSTATUS_CONN_ERROR, + TSTATUS_OTHER_ERROR +} TStatus; + +/* Various random sequences are initialized from this one. */ +static pg_prng_state base_random_sequence; + +/* Synchronization barrier for start and connection */ +static THREAD_BARRIER_T barrier; + +/* + * Connection state machine states. + */ +typedef enum +{ + /* + * The client must first choose a script to execute. Once chosen, it can + * either be throttled (state CSTATE_PREPARE_THROTTLE under --rate), start + * right away (state CSTATE_START_TX) or not start at all if the timer was + * exceeded (state CSTATE_FINISHED). + */ + CSTATE_CHOOSE_SCRIPT, + + /* + * CSTATE_START_TX performs start-of-transaction processing. Establishes + * a new connection for the transaction in --connect mode, records the + * transaction start time, and proceed to the first command. + * + * Note: once a script is started, it will either error or run till its + * end, where it may be interrupted. It is not interrupted while running, + * so pgbench --time is to be understood as tx are allowed to start in + * that time, and will finish when their work is completed. + */ + CSTATE_START_TX, + + /* + * In CSTATE_PREPARE_THROTTLE state, we calculate when to begin the next + * transaction, and advance to CSTATE_THROTTLE. CSTATE_THROTTLE state + * sleeps until that moment, then advances to CSTATE_START_TX, or + * CSTATE_FINISHED if the next transaction would start beyond the end of + * the run. + */ + CSTATE_PREPARE_THROTTLE, + CSTATE_THROTTLE, + + /* + * We loop through these states, to process each command in the script: + * + * CSTATE_START_COMMAND starts the execution of a command. On a SQL + * command, the command is sent to the server, and we move to + * CSTATE_WAIT_RESULT state unless in pipeline mode. On a \sleep + * meta-command, the timer is set, and we enter the CSTATE_SLEEP state to + * wait for it to expire. Other meta-commands are executed immediately. If + * the command about to start is actually beyond the end of the script, + * advance to CSTATE_END_TX. + * + * CSTATE_WAIT_RESULT waits until we get a result set back from the server + * for the current command. + * + * CSTATE_SLEEP waits until the end of \sleep. + * + * CSTATE_END_COMMAND records the end-of-command timestamp, increments the + * command counter, and loops back to CSTATE_START_COMMAND state. + * + * CSTATE_SKIP_COMMAND is used by conditional branches which are not + * executed. It quickly skip commands that do not need any evaluation. + * This state can move forward several commands, till there is something + * to do or the end of the script. + */ + CSTATE_START_COMMAND, + CSTATE_WAIT_RESULT, + CSTATE_SLEEP, + CSTATE_END_COMMAND, + CSTATE_SKIP_COMMAND, + + /* + * States for failed commands. + * + * If the SQL/meta command fails, in CSTATE_ERROR clean up after an error: + * (1) clear the conditional stack; (2) if we have an unterminated + * (possibly failed) transaction block, send the rollback command to the + * server and wait for the result in CSTATE_WAIT_ROLLBACK_RESULT. If + * something goes wrong with rolling back, go to CSTATE_ABORTED. + * + * But if everything is ok we are ready for future transactions: if this + * is a serialization or deadlock error and we can re-execute the + * transaction from the very beginning, go to CSTATE_RETRY; otherwise go + * to CSTATE_FAILURE. + * + * In CSTATE_RETRY report an error, set the same parameters for the + * transaction execution as in the previous tries and process the first + * transaction command in CSTATE_START_COMMAND. + * + * In CSTATE_FAILURE report a failure, set the parameters for the + * transaction execution as they were before the first run of this + * transaction (except for a random state) and go to CSTATE_END_TX to + * complete this transaction. + */ + CSTATE_ERROR, + CSTATE_WAIT_ROLLBACK_RESULT, + CSTATE_RETRY, + CSTATE_FAILURE, + + /* + * CSTATE_END_TX performs end-of-transaction processing. It calculates + * latency, and logs the transaction. In --connect mode, it closes the + * current connection. + * + * Then either starts over in CSTATE_CHOOSE_SCRIPT, or enters + * CSTATE_FINISHED if we have no more work to do. + */ + CSTATE_END_TX, + + /* + * Final states. CSTATE_ABORTED means that the script execution was + * aborted because a command failed, CSTATE_FINISHED means success. + */ + CSTATE_ABORTED, + CSTATE_FINISHED +} ConnectionStateEnum; + +/* + * Connection state. + */ +typedef struct +{ + PGconn *con; /* connection handle to DB */ + int id; /* client No. */ + ConnectionStateEnum state; /* state machine's current state. */ + ConditionalStack cstack; /* enclosing conditionals state */ + + /* + * Separate randomness for each client. This is used for random functions + * PGBENCH_RANDOM_* during the execution of the script. + */ + pg_prng_state cs_func_rs; + + int use_file; /* index in sql_script for this client */ + int command; /* command number in script */ + + /* client variables */ + Variables variables; + + /* various times about current transaction in microseconds */ + pg_time_usec_t txn_scheduled; /* scheduled start time of transaction */ + pg_time_usec_t sleep_until; /* scheduled start time of next cmd */ + pg_time_usec_t txn_begin; /* used for measuring schedule lag times */ + pg_time_usec_t stmt_begin; /* used for measuring statement latencies */ + + /* whether client prepared each command of each script */ + bool **prepared; + + /* + * For processing failures and repeating transactions with serialization + * or deadlock errors: + */ + EStatus estatus; /* the error status of the current transaction + * execution; this is ESTATUS_NO_ERROR if + * there were no errors */ + pg_prng_state random_state; /* random state */ + uint32 tries; /* how many times have we already tried the + * current transaction? */ + + /* per client collected stats */ + int64 cnt; /* client transaction count, for -t; skipped + * and failed transactions are also counted + * here */ +} CState; + +/* + * Thread state + */ +typedef struct +{ + int tid; /* thread id */ + THREAD_T thread; /* thread handle */ + CState *state; /* array of CState */ + int nstate; /* length of state[] */ + + /* + * Separate randomness for each thread. Each thread option uses its own + * random state to make all of them independent of each other and + * therefore deterministic at the thread level. + */ + pg_prng_state ts_choose_rs; /* random state for selecting a script */ + pg_prng_state ts_throttle_rs; /* random state for transaction throttling */ + pg_prng_state ts_sample_rs; /* random state for log sampling */ + + int64 throttle_trigger; /* previous/next throttling (us) */ + FILE *logfile; /* where to log, or NULL */ + + /* per thread collected stats in microseconds */ + pg_time_usec_t create_time; /* thread creation time */ + pg_time_usec_t started_time; /* thread is running */ + pg_time_usec_t bench_start; /* thread is benchmarking */ + pg_time_usec_t conn_duration; /* cumulated connection and disconnection + * delays */ + + StatsData stats; + int64 latency_late; /* count executed but late transactions */ +} TState; + +/* + * queries read from files + */ +#define SQL_COMMAND 1 +#define META_COMMAND 2 + +/* + * max number of backslash command arguments or SQL variables, + * including the command or SQL statement itself + */ +#define MAX_ARGS 256 + +typedef enum MetaCommand +{ + META_NONE, /* not a known meta-command */ + META_SET, /* \set */ + META_SETSHELL, /* \setshell */ + META_SHELL, /* \shell */ + META_SLEEP, /* \sleep */ + META_GSET, /* \gset */ + META_ASET, /* \aset */ + META_IF, /* \if */ + META_ELIF, /* \elif */ + META_ELSE, /* \else */ + META_ENDIF, /* \endif */ + META_STARTPIPELINE, /* \startpipeline */ + META_ENDPIPELINE /* \endpipeline */ +} MetaCommand; + +typedef enum QueryMode +{ + QUERY_SIMPLE, /* simple query */ + QUERY_EXTENDED, /* extended query */ + QUERY_PREPARED, /* extended query with prepared statements */ + NUM_QUERYMODE +} QueryMode; + +static QueryMode querymode = QUERY_SIMPLE; +static const char *QUERYMODE[] = {"simple", "extended", "prepared"}; + +/* + * struct Command represents one command in a script. + * + * lines The raw, possibly multi-line command text. Variable substitution + * not applied. + * first_line A short, single-line extract of 'lines', for error reporting. + * type SQL_COMMAND or META_COMMAND + * meta The type of meta-command, with META_NONE/GSET/ASET if command + * is SQL. + * argc Number of arguments of the command, 0 if not yet processed. + * argv Command arguments, the first of which is the command or SQL + * string itself. For SQL commands, after post-processing + * argv[0] is the same as 'lines' with variables substituted. + * prepname The name that this command is prepared under, in prepare mode + * varprefix SQL commands terminated with \gset or \aset have this set + * to a non NULL value. If nonempty, it's used to prefix the + * variable name that receives the value. + * aset do gset on all possible queries of a combined query (\;). + * expr Parsed expression, if needed. + * stats Time spent in this command. + * retries Number of retries after a serialization or deadlock error in the + * current command. + * failures Number of errors in the current command that were not retried. + */ +typedef struct Command +{ + PQExpBufferData lines; + char *first_line; + int type; + MetaCommand meta; + int argc; + char *argv[MAX_ARGS]; + char *prepname; + char *varprefix; + PgBenchExpr *expr; + SimpleStats stats; + int64 retries; + int64 failures; +} Command; + +typedef struct ParsedScript +{ + const char *desc; /* script descriptor (eg, file name) */ + int weight; /* selection weight */ + Command **commands; /* NULL-terminated array of Commands */ + StatsData stats; /* total time spent in script */ +} ParsedScript; + +static ParsedScript sql_script[MAX_SCRIPTS]; /* SQL script files */ +static int num_scripts; /* number of scripts in sql_script[] */ +static int64 total_weight = 0; + +static bool verbose_errors = false; /* print verbose messages of all errors */ + +/* Builtin test scripts */ +typedef struct BuiltinScript +{ + const char *name; /* very short name for -b ... */ + const char *desc; /* short description */ + const char *script; /* actual pgbench script */ +} BuiltinScript; + +static const BuiltinScript builtin_script[] = +{ + { + "tpcb-like", + "<builtin: TPC-B (sort of)>", + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" + "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n" + "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n" + "\\set delta random(-5000, 5000)\n" + "BEGIN;\n" + "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n" + "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" + "UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid;\n" + "UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid;\n" + "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n" + "END;\n" + }, + { + "simple-update", + "<builtin: simple update>", + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" + "\\set bid random(1, " CppAsString2(nbranches) " * :scale)\n" + "\\set tid random(1, " CppAsString2(ntellers) " * :scale)\n" + "\\set delta random(-5000, 5000)\n" + "BEGIN;\n" + "UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid;\n" + "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" + "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP);\n" + "END;\n" + }, + { + "select-only", + "<builtin: select only>", + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" + "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" + } +}; + + +/* 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(CState *st, PgBenchExpr *expr, + PgBenchValue *retval); +static ConnectionStateEnum executeMetaCommand(CState *st, pg_time_usec_t *now); +static void doLog(TState *thread, CState *st, + StatsData *agg, bool skipped, double latency, double lag); +static void processXactStats(TState *thread, CState *st, pg_time_usec_t *now, + bool skipped, StatsData *agg); +static void addScript(const ParsedScript *script); +static THREAD_FUNC_RETURN_TYPE THREAD_FUNC_CC threadRun(void *arg); +static void finishCon(CState *st); +static void setalarm(int seconds); +static socket_set *alloc_socket_set(int count); +static void free_socket_set(socket_set *sa); +static void clear_socket_set(socket_set *sa); +static void add_socket_to_set(socket_set *sa, int fd, int idx); +static int wait_on_socket_set(socket_set *sa, int64 usecs); +static bool socket_has_input(socket_set *sa, int fd, int idx); + + +/* callback functions for our flex lexer */ +static const PsqlScanCallbacks pgbench_callbacks = { + NULL, /* don't need get_variable functionality */ +}; + +static inline pg_time_usec_t +pg_time_now(void) +{ + instr_time now; + + INSTR_TIME_SET_CURRENT(now); + + return (pg_time_usec_t) INSTR_TIME_GET_MICROSEC(now); +} + +static inline void +pg_time_now_lazy(pg_time_usec_t *now) +{ + if ((*now) == 0) + (*now) = pg_time_now(); +} + +#define PG_TIME_GET_DOUBLE(t) (0.000001 * (t)) + +static void +usage(void) +{ + printf("%s is a benchmarking tool for PostgreSQL.\n\n" + "Usage:\n" + " %s [OPTION]... [DBNAME]\n" + "\nInitialization options:\n" + " -i, --initialize invokes initialization mode\n" + " -I, --init-steps=[" ALL_INIT_STEPS "]+ (default \"" DEFAULT_INIT_STEPS "\")\n" + " run selected initialization steps\n" + " -F, --fillfactor=NUM set fill factor\n" + " -n, --no-vacuum do not run VACUUM during initialization\n" + " -q, --quiet quiet logging (one message each 5 seconds)\n" + " -s, --scale=NUM scaling factor\n" + " --foreign-keys create foreign key constraints between tables\n" + " --index-tablespace=TABLESPACE\n" + " create indexes in the specified tablespace\n" + " --partition-method=(range|hash)\n" + " partition pgbench_accounts with this method (default: range)\n" + " --partitions=NUM partition pgbench_accounts into NUM parts (default: 0)\n" + " --tablespace=TABLESPACE create tables in the specified tablespace\n" + " --unlogged-tables create tables as unlogged tables\n" + "\nOptions to select what to run:\n" + " -b, --builtin=NAME[@W] add builtin script NAME weighted at W (default: 1)\n" + " (use \"-b list\" to list available scripts)\n" + " -f, --file=FILENAME[@W] add script FILENAME weighted at W (default: 1)\n" + " -N, --skip-some-updates skip updates of pgbench_tellers and pgbench_branches\n" + " (same as \"-b simple-update\")\n" + " -S, --select-only perform SELECT-only transactions\n" + " (same as \"-b select-only\")\n" + "\nBenchmarking options:\n" + " -c, --client=NUM number of concurrent database clients (default: 1)\n" + " -C, --connect establish new connection for each transaction\n" + " -D, --define=VARNAME=VALUE\n" + " define variable for use by custom script\n" + " -j, --jobs=NUM number of threads (default: 1)\n" + " -l, --log write transaction times to log file\n" + " -L, --latency-limit=NUM count transactions lasting more than NUM ms as late\n" + " -M, --protocol=simple|extended|prepared\n" + " protocol for submitting queries (default: simple)\n" + " -n, --no-vacuum do not run VACUUM before tests\n" + " -P, --progress=NUM show thread progress report every NUM seconds\n" + " -r, --report-per-command report latencies, failures, and retries per command\n" + " -R, --rate=NUM target rate in transactions per second\n" + " -s, --scale=NUM report this scale factor in output\n" + " -t, --transactions=NUM number of transactions each client runs (default: 10)\n" + " -T, --time=NUM duration of benchmark test in seconds\n" + " -v, --vacuum-all vacuum all four standard tables before tests\n" + " --aggregate-interval=NUM aggregate data over NUM seconds\n" + " --failures-detailed report the failures grouped by basic types\n" + " --log-prefix=PREFIX prefix for transaction time log file\n" + " (default: \"pgbench_log\")\n" + " --max-tries=NUM max number of tries to run transaction (default: 1)\n" + " --progress-timestamp use Unix epoch timestamps for progress\n" + " --random-seed=SEED set random seed (\"time\", \"rand\", integer)\n" + " --sampling-rate=NUM fraction of transactions to log (e.g., 0.01 for 1%%)\n" + " --show-script=NAME show builtin script code, then exit\n" + " --verbose-errors print messages of all errors\n" + "\nCommon options:\n" + " -d, --debug print debugging output\n" + " -h, --host=HOSTNAME database server host or socket directory\n" + " -p, --port=PORT database server port number\n" + " -U, --username=USERNAME connect as specified database user\n" + " -V, --version output version information, then exit\n" + " -?, --help show this help, then exit\n" + "\n" + "Report bugs to <%s>.\n" + "%s home page: <%s>\n", + progname, progname, PACKAGE_BUGREPORT, PACKAGE_NAME, PACKAGE_URL); +} + +/* return whether str matches "^\s*[-+]?[0-9]+$" */ +static bool +is_an_int(const char *str) +{ + const char *ptr = str; + + /* skip leading spaces; cast is consistent with strtoint64 */ + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + + /* skip sign */ + if (*ptr == '+' || *ptr == '-') + ptr++; + + /* at least one digit */ + if (*ptr && !isdigit((unsigned char) *ptr)) + return false; + + /* eat all digits */ + while (*ptr && isdigit((unsigned char) *ptr)) + ptr++; + + /* must have reached end of string */ + return *ptr == '\0'; +} + + +/* + * strtoint64 -- convert a string to 64-bit integer + * + * This function is a slightly modified version of pg_strtoint64() from + * src/backend/utils/adt/numutils.c. + * + * The function returns whether the conversion worked, and if so + * "*result" is set to the result. + * + * If not errorOK, an error message is also printed out on errors. + */ +bool +strtoint64(const char *str, bool errorOK, int64 *result) +{ + const char *ptr = str; + int64 tmp = 0; + bool neg = false; + + /* + * Do our own scan, rather than relying on sscanf which might be broken + * for long long. + * + * As INT64_MIN can't be stored as a positive 64 bit integer, accumulate + * value as a negative number. + */ + + /* skip leading spaces */ + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + + /* handle sign */ + if (*ptr == '-') + { + ptr++; + neg = true; + } + else if (*ptr == '+') + ptr++; + + /* require at least one digit */ + if (unlikely(!isdigit((unsigned char) *ptr))) + goto invalid_syntax; + + /* process digits */ + while (*ptr && isdigit((unsigned char) *ptr)) + { + int8 digit = (*ptr++ - '0'); + + if (unlikely(pg_mul_s64_overflow(tmp, 10, &tmp)) || + unlikely(pg_sub_s64_overflow(tmp, digit, &tmp))) + goto out_of_range; + } + + /* allow trailing whitespace, but not other trailing chars */ + while (*ptr != '\0' && isspace((unsigned char) *ptr)) + ptr++; + + if (unlikely(*ptr != '\0')) + goto invalid_syntax; + + if (!neg) + { + if (unlikely(tmp == PG_INT64_MIN)) + goto out_of_range; + tmp = -tmp; + } + + *result = tmp; + return true; + +out_of_range: + if (!errorOK) + pg_log_error("value \"%s\" is out of range for type bigint", str); + return false; + +invalid_syntax: + if (!errorOK) + pg_log_error("invalid input syntax for type bigint: \"%s\"", str); + return false; +} + +/* convert string to double, detecting overflows/underflows */ +bool +strtodouble(const char *str, bool errorOK, double *dv) +{ + char *end; + + errno = 0; + *dv = strtod(str, &end); + + if (unlikely(errno != 0)) + { + if (!errorOK) + pg_log_error("value \"%s\" is out of range for type double", str); + return false; + } + + if (unlikely(end == str || *end != '\0')) + { + if (!errorOK) + pg_log_error("invalid input syntax for type double: \"%s\"", str); + return false; + } + return true; +} + +/* + * Initialize a prng state struct. + * + * We derive the seed from base_random_sequence, which must be set up already. + */ +static void +initRandomState(pg_prng_state *state) +{ + pg_prng_seed(state, pg_prng_uint64(&base_random_sequence)); +} + + +/* + * random number generator: uniform distribution from min to max inclusive. + * + * Although the limits are expressed as int64, you can't generate the full + * int64 range in one call, because the difference of the limits mustn't + * overflow int64. This is not checked. + */ +static int64 +getrand(pg_prng_state *state, int64 min, int64 max) +{ + return min + (int64) pg_prng_uint64_range(state, 0, max - min); +} + +/* + * random number generator: exponential distribution from min to max inclusive. + * the parameter is so that the density of probability for the last cut-off max + * value is exp(-parameter). + */ +static int64 +getExponentialRand(pg_prng_state *state, int64 min, int64 max, + double parameter) +{ + double cut, + uniform, + rand; + + /* abort if wrong parameter, but must really be checked beforehand */ + Assert(parameter > 0.0); + cut = exp(-parameter); + /* pg_prng_double value in [0, 1), uniform in (0, 1] */ + uniform = 1.0 - pg_prng_double(state); + + /* + * inner expression in (cut, 1] (if parameter > 0), rand in [0, 1) + */ + Assert((1.0 - cut) != 0.0); + rand = -log(cut + (1.0 - cut) * uniform) / parameter; + /* return int64 random number within between min and max */ + return min + (int64) ((max - min + 1) * rand); +} + +/* random number generator: gaussian distribution from min to max inclusive */ +static int64 +getGaussianRand(pg_prng_state *state, int64 min, int64 max, + double parameter) +{ + double stdev; + double rand; + + /* abort if parameter is too low, but must really be checked beforehand */ + Assert(parameter >= MIN_GAUSSIAN_PARAM); + + /* + * Get user specified random number from this loop, with -parameter < + * stdev <= parameter + * + * This loop is executed until the number is in the expected range. + * + * As the minimum parameter is 2.0, the probability of looping is low: + * sqrt(-2 ln(r)) <= 2 => r >= e^{-2} ~ 0.135, then when taking the + * average sinus multiplier as 2/pi, we have a 8.6% looping probability in + * the worst case. For a parameter value of 5.0, the looping probability + * is about e^{-5} * 2 / pi ~ 0.43%. + */ + do + { + /* + * pg_prng_double generates [0, 1), but for the basic version of the + * Box-Muller transform the two uniformly distributed random numbers + * are expected to be in (0, 1] (see + * https://en.wikipedia.org/wiki/Box-Muller_transform) + */ + double rand1 = 1.0 - pg_prng_double(state); + double rand2 = 1.0 - pg_prng_double(state); + + /* Box-Muller basic form transform */ + double var_sqrt = sqrt(-2.0 * log(rand1)); + + stdev = var_sqrt * sin(2.0 * M_PI * rand2); + + /* + * we may try with cos, but there may be a bias induced if the + * previous value fails the test. To be on the safe side, let us try + * over. + */ + } + while (stdev < -parameter || stdev >= parameter); + + /* stdev is in [-parameter, parameter), normalization to [0,1) */ + rand = (stdev + parameter) / (parameter * 2.0); + + /* return int64 random number within between min and max */ + return min + (int64) ((max - min + 1) * rand); +} + +/* + * random number generator: generate a value, such that the series of values + * will approximate a Poisson distribution centered on the given value. + * + * Individual results are rounded to integers, though the center value need + * not be one. + */ +static int64 +getPoissonRand(pg_prng_state *state, double center) +{ + /* + * Use inverse transform sampling to generate a value > 0, such that the + * expected (i.e. average) value is the given argument. + */ + double uniform; + + /* pg_prng_double value in [0, 1), uniform in (0, 1] */ + uniform = 1.0 - pg_prng_double(state); + + return (int64) (-log(uniform) * center + 0.5); +} + +/* + * Computing zipfian using rejection method, based on + * "Non-Uniform Random Variate Generation", + * Luc Devroye, p. 550-551, Springer 1986. + * + * This works for s > 1.0, but may perform badly for s very close to 1.0. + */ +static int64 +computeIterativeZipfian(pg_prng_state *state, int64 n, double s) +{ + double b = pow(2.0, s - 1.0); + double x, + t, + u, + v; + + /* Ensure n is sane */ + if (n <= 1) + return 1; + + while (true) + { + /* random variates */ + u = pg_prng_double(state); + v = pg_prng_double(state); + + x = floor(pow(u, -1.0 / (s - 1.0))); + + t = pow(1.0 + 1.0 / x, s - 1.0); + /* reject if too large or out of bound */ + if (v * x * (t - 1.0) / (b - 1.0) <= t / b && x <= n) + break; + } + return (int64) x; +} + +/* random number generator: zipfian distribution from min to max inclusive */ +static int64 +getZipfianRand(pg_prng_state *state, int64 min, int64 max, double s) +{ + int64 n = max - min + 1; + + /* abort if parameter is invalid */ + Assert(MIN_ZIPFIAN_PARAM <= s && s <= MAX_ZIPFIAN_PARAM); + + return min - 1 + computeIterativeZipfian(state, n, s); +} + +/* + * FNV-1a hash function + */ +static int64 +getHashFnv1a(int64 val, uint64 seed) +{ + int64 result; + int i; + + result = FNV_OFFSET_BASIS ^ seed; + for (i = 0; i < 8; ++i) + { + int32 octet = val & 0xff; + + val = val >> 8; + result = result ^ octet; + result = result * FNV_PRIME; + } + + return result; +} + +/* + * Murmur2 hash function + * + * Based on original work of Austin Appleby + * https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp + */ +static int64 +getHashMurmur2(int64 val, uint64 seed) +{ + uint64 result = seed ^ MM2_MUL_TIMES_8; /* sizeof(int64) */ + uint64 k = (uint64) val; + + k *= MM2_MUL; + k ^= k >> MM2_ROT; + k *= MM2_MUL; + + result ^= k; + result *= MM2_MUL; + + result ^= result >> MM2_ROT; + result *= MM2_MUL; + result ^= result >> MM2_ROT; + + return (int64) result; +} + +/* + * Pseudorandom permutation function + * + * For small sizes, this generates each of the (size!) possible permutations + * of integers in the range [0, size) with roughly equal probability. Once + * the size is larger than 20, the number of possible permutations exceeds the + * number of distinct states of the internal pseudorandom number generator, + * and so not all possible permutations can be generated, but the permutations + * chosen should continue to give the appearance of being random. + * + * THIS FUNCTION IS NOT CRYPTOGRAPHICALLY SECURE. + * DO NOT USE FOR SUCH PURPOSE. + */ +static int64 +permute(const int64 val, const int64 isize, const int64 seed) +{ + /* using a high-end PRNG is probably overkill */ + pg_prng_state state; + uint64 size; + uint64 v; + int masklen; + uint64 mask; + int i; + + if (isize < 2) + return 0; /* nothing to permute */ + + /* Initialize prng state using the seed */ + pg_prng_seed(&state, (uint64) seed); + + /* Computations are performed on unsigned values */ + size = (uint64) isize; + v = (uint64) val % size; + + /* Mask to work modulo largest power of 2 less than or equal to size */ + masklen = pg_leftmost_one_pos64(size); + mask = (((uint64) 1) << masklen) - 1; + + /* + * Permute the input value by applying several rounds of pseudorandom + * bijective transformations. The intention here is to distribute each + * input uniformly randomly across the range, and separate adjacent inputs + * approximately uniformly randomly from each other, leading to a fairly + * random overall choice of permutation. + * + * To separate adjacent inputs, we multiply by a random number modulo + * (mask + 1), which is a power of 2. For this to be a bijection, the + * multiplier must be odd. Since this is known to lead to less randomness + * in the lower bits, we also apply a rotation that shifts the topmost bit + * into the least significant bit. In the special cases where size <= 3, + * mask = 1 and each of these operations is actually a no-op, so we also + * XOR the value with a different random number to inject additional + * randomness. Since the size is generally not a power of 2, we apply + * this bijection on overlapping upper and lower halves of the input. + * + * To distribute the inputs uniformly across the range, we then also apply + * a random offset modulo the full range. + * + * Taken together, these operations resemble a modified linear + * congruential generator, as is commonly used in pseudorandom number + * generators. The number of rounds is fairly arbitrary, but six has been + * found empirically to give a fairly good tradeoff between performance + * and uniform randomness. For small sizes it selects each of the (size!) + * possible permutations with roughly equal probability. For larger + * sizes, not all permutations can be generated, but the intended random + * spread is still produced. + */ + for (i = 0; i < 6; i++) + { + uint64 m, + r, + t; + + /* Random multiply (by an odd number), XOR and rotate of lower half */ + m = (pg_prng_uint64(&state) & mask) | 1; + r = pg_prng_uint64(&state) & mask; + if (v <= mask) + { + v = ((v * m) ^ r) & mask; + v = ((v << 1) & mask) | (v >> (masklen - 1)); + } + + /* Random multiply (by an odd number), XOR and rotate of upper half */ + m = (pg_prng_uint64(&state) & mask) | 1; + r = pg_prng_uint64(&state) & mask; + t = size - 1 - v; + if (t <= mask) + { + t = ((t * m) ^ r) & mask; + t = ((t << 1) & mask) | (t >> (masklen - 1)); + v = size - 1 - t; + } + + /* Random offset */ + r = pg_prng_uint64_range(&state, 0, size - 1); + v = (v + r) % size; + } + + return (int64) v; +} + +/* + * Initialize the given SimpleStats struct to all zeroes + */ +static void +initSimpleStats(SimpleStats *ss) +{ + memset(ss, 0, sizeof(SimpleStats)); +} + +/* + * Accumulate one value into a SimpleStats struct. + */ +static void +addToSimpleStats(SimpleStats *ss, double val) +{ + if (ss->count == 0 || val < ss->min) + ss->min = val; + if (ss->count == 0 || val > ss->max) + ss->max = val; + ss->count++; + ss->sum += val; + ss->sum2 += val * val; +} + +/* + * Merge two SimpleStats objects + */ +static void +mergeSimpleStats(SimpleStats *acc, SimpleStats *ss) +{ + if (acc->count == 0 || ss->min < acc->min) + acc->min = ss->min; + if (acc->count == 0 || ss->max > acc->max) + acc->max = ss->max; + acc->count += ss->count; + acc->sum += ss->sum; + acc->sum2 += ss->sum2; +} + +/* + * Initialize a StatsData struct to mostly zeroes, with its start time set to + * the given value. + */ +static void +initStats(StatsData *sd, pg_time_usec_t start) +{ + sd->start_time = start; + sd->cnt = 0; + sd->skipped = 0; + sd->retries = 0; + sd->retried = 0; + sd->serialization_failures = 0; + sd->deadlock_failures = 0; + initSimpleStats(&sd->latency); + initSimpleStats(&sd->lag); +} + +/* + * Accumulate one additional item into the given stats object. + */ +static void +accumStats(StatsData *stats, bool skipped, double lat, double lag, + EStatus estatus, int64 tries) +{ + /* Record the skipped transaction */ + if (skipped) + { + /* no latency to record on skipped transactions */ + stats->skipped++; + return; + } + + /* + * Record the number of retries regardless of whether the transaction was + * successful or failed. + */ + if (tries > 1) + { + stats->retries += (tries - 1); + stats->retried++; + } + + switch (estatus) + { + /* Record the successful transaction */ + case ESTATUS_NO_ERROR: + stats->cnt++; + + addToSimpleStats(&stats->latency, lat); + + /* and possibly the same for schedule lag */ + if (throttle_delay) + addToSimpleStats(&stats->lag, lag); + break; + + /* Record the failed transaction */ + case ESTATUS_SERIALIZATION_ERROR: + stats->serialization_failures++; + break; + case ESTATUS_DEADLOCK_ERROR: + stats->deadlock_failures++; + break; + default: + /* internal error which should never occur */ + pg_fatal("unexpected error status: %d", estatus); + } +} + +/* call PQexec() and exit() on failure */ +static void +executeStatement(PGconn *con, const char *sql) +{ + PGresult *res; + + res = PQexec(con, sql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("query failed: %s", PQerrorMessage(con)); + pg_log_error_detail("Query was: %s", sql); + exit(1); + } + PQclear(res); +} + +/* call PQexec() and complain, but without exiting, on failure */ +static void +tryExecuteStatement(PGconn *con, const char *sql) +{ + PGresult *res; + + res = PQexec(con, sql); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + pg_log_error("%s", PQerrorMessage(con)); + pg_log_error_detail("(ignoring this error and continuing anyway)"); + } + PQclear(res); +} + +/* set up a connection to the backend */ +static PGconn * +doConnect(void) +{ + PGconn *conn; + bool new_pass; + static char *password = NULL; + + /* + * Start the connection. Loop until we have a password if requested by + * backend. + */ + do + { +#define PARAMS_ARRAY_SIZE 7 + + const char *keywords[PARAMS_ARRAY_SIZE]; + const char *values[PARAMS_ARRAY_SIZE]; + + keywords[0] = "host"; + values[0] = pghost; + keywords[1] = "port"; + values[1] = pgport; + keywords[2] = "user"; + values[2] = username; + keywords[3] = "password"; + values[3] = password; + keywords[4] = "dbname"; + values[4] = dbName; + keywords[5] = "fallback_application_name"; + values[5] = progname; + keywords[6] = NULL; + values[6] = NULL; + + new_pass = false; + + conn = PQconnectdbParams(keywords, values, true); + + if (!conn) + { + pg_log_error("connection to database \"%s\" failed", dbName); + return NULL; + } + + if (PQstatus(conn) == CONNECTION_BAD && + PQconnectionNeedsPassword(conn) && + !password) + { + PQfinish(conn); + password = simple_prompt("Password: ", false); + new_pass = true; + } + } while (new_pass); + + /* check to see that the backend connection was successfully made */ + if (PQstatus(conn) == CONNECTION_BAD) + { + pg_log_error("%s", PQerrorMessage(conn)); + PQfinish(conn); + return NULL; + } + + return conn; +} + +/* qsort comparator for Variable array */ +static int +compareVariableNames(const void *v1, const void *v2) +{ + return strcmp(((const Variable *) v1)->name, + ((const Variable *) v2)->name); +} + +/* Locate a variable by name; returns NULL if unknown */ +static Variable * +lookupVariable(Variables *variables, char *name) +{ + Variable key; + + /* On some versions of Solaris, bsearch of zero items dumps core */ + if (variables->nvars <= 0) + return NULL; + + /* Sort if we have to */ + if (!variables->vars_sorted) + { + qsort((void *) variables->vars, variables->nvars, sizeof(Variable), + compareVariableNames); + variables->vars_sorted = true; + } + + /* Now we can search */ + key.name = name; + return (Variable *) bsearch((void *) &key, + (void *) variables->vars, + variables->nvars, + sizeof(Variable), + compareVariableNames); +} + +/* Get the value of a variable, in string form; returns NULL if unknown */ +static char * +getVariable(Variables *variables, char *name) +{ + Variable *var; + char stringform[64]; + + var = lookupVariable(variables, name); + if (var == NULL) + return NULL; /* not found */ + + if (var->svalue) + return var->svalue; /* we have it in string form */ + + /* 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), + "%s", var->value.u.bval ? "true" : "false"); + else if (var->value.type == PGBT_INT) + snprintf(stringform, sizeof(stringform), + 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 a value; return false on failure */ +static bool +makeVariableValue(Variable *var) +{ + size_t slen; + + if (var->value.type != PGBT_NO_VALUE) + return true; /* no work */ + + slen = strlen(var->svalue); + + if (slen == 0) + /* what should it do on ""? */ + return false; + + if (pg_strcasecmp(var->svalue, "null") == 0) + { + setNullValue(&var->value); + } + + /* + * accept prefixes such as y, ye, n, no... but not for "o". 0/1 are + * recognized later as an int, which is converted to bool if needed. + */ + else if (pg_strncasecmp(var->svalue, "true", slen) == 0 || + pg_strncasecmp(var->svalue, "yes", slen) == 0 || + pg_strcasecmp(var->svalue, "on") == 0) + { + setBoolValue(&var->value, true); + } + else if (pg_strncasecmp(var->svalue, "false", slen) == 0 || + pg_strncasecmp(var->svalue, "no", slen) == 0 || + pg_strcasecmp(var->svalue, "off") == 0 || + pg_strcasecmp(var->svalue, "of") == 0) + { + setBoolValue(&var->value, false); + } + else if (is_an_int(var->svalue)) + { + /* if it looks like an int, it must be an int without overflow */ + int64 iv; + + if (!strtoint64(var->svalue, false, &iv)) + return false; + + setIntValue(&var->value, iv); + } + else /* type should be double */ + { + double dv; + + if (!strtodouble(var->svalue, true, &dv)) + { + pg_log_error("malformed variable \"%s\" value: \"%s\"", + var->name, var->svalue); + return false; + } + setDoubleValue(&var->value, dv); + } + return true; +} + +/* + * Check whether a variable's name is allowed. + * + * We allow any non-ASCII character, as well as ASCII letters, digits, and + * underscore. + * + * Keep this in sync with the definitions of variable name characters in + * "src/fe_utils/psqlscan.l", "src/bin/psql/psqlscanslash.l" and + * "src/bin/pgbench/exprscan.l". Also see parseVariable(), below. + * + * Note: this static function is copied from "src/bin/psql/variables.c" + * but changed to disallow variable names starting with a digit. + */ +static bool +valid_variable_name(const char *name) +{ + const unsigned char *ptr = (const unsigned char *) name; + + /* Mustn't be zero-length */ + if (*ptr == '\0') + return false; + + /* must not start with [0-9] */ + if (IS_HIGHBIT_SET(*ptr) || + strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" + "_", *ptr) != NULL) + ptr++; + else + return false; + + /* remaining characters can include [0-9] */ + while (*ptr) + { + if (IS_HIGHBIT_SET(*ptr) || + strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" + "_0123456789", *ptr) != NULL) + ptr++; + else + return false; + } + + return true; +} + +/* + * Make sure there is enough space for 'needed' more variable in the variables + * array. + */ +static void +enlargeVariables(Variables *variables, int needed) +{ + /* total number of variables required now */ + needed += variables->nvars; + + if (variables->max_vars < needed) + { + variables->max_vars = needed + VARIABLES_ALLOC_MARGIN; + variables->vars = (Variable *) + pg_realloc(variables->vars, variables->max_vars * sizeof(Variable)); + } +} + +/* + * Lookup a variable by name, creating it if need be. + * Caller is expected to assign a value to the variable. + * Returns NULL on failure (bad name). + */ +static Variable * +lookupCreateVariable(Variables *variables, const char *context, char *name) +{ + Variable *var; + + var = lookupVariable(variables, name); + if (var == NULL) + { + /* + * Check for the name only when declaring a new variable to avoid + * overhead. + */ + if (!valid_variable_name(name)) + { + pg_log_error("%s: invalid variable name: \"%s\"", context, name); + return NULL; + } + + /* Create variable at the end of the array */ + enlargeVariables(variables, 1); + + var = &(variables->vars[variables->nvars]); + + var->name = pg_strdup(name); + var->svalue = NULL; + /* caller is expected to initialize remaining fields */ + + variables->nvars++; + /* we don't re-sort the array till we have to */ + variables->vars_sorted = false; + } + + return var; +} + +/* Assign a string value to a variable, creating it if need be */ +/* Returns false on failure (bad name) */ +static bool +putVariable(Variables *variables, const char *context, char *name, + const char *value) +{ + Variable *var; + char *val; + + var = lookupCreateVariable(variables, context, name); + if (!var) + return false; + + /* dup then free, in case value is pointing at this variable */ + val = pg_strdup(value); + + if (var->svalue) + free(var->svalue); + var->svalue = val; + var->value.type = PGBT_NO_VALUE; + + return true; +} + +/* Assign a value to a variable, creating it if need be */ +/* Returns false on failure (bad name) */ +static bool +putVariableValue(Variables *variables, const char *context, char *name, + const PgBenchValue *value) +{ + Variable *var; + + var = lookupCreateVariable(variables, context, name); + if (!var) + return false; + + if (var->svalue) + free(var->svalue); + var->svalue = NULL; + var->value = *value; + + return true; +} + +/* Assign an integer value to a variable, creating it if need be */ +/* Returns false on failure (bad name) */ +static bool +putVariableInt(Variables *variables, const char *context, char *name, + int64 value) +{ + PgBenchValue val; + + setIntValue(&val, value); + return putVariableValue(variables, context, name, &val); +} + +/* + * Parse a possible variable reference (:varname). + * + * "sql" points at a colon. If what follows it looks like a valid + * variable name, return a malloc'd string containing the variable name, + * and set *eaten to the number of characters consumed (including the colon). + * Otherwise, return NULL. + */ +static char * +parseVariable(const char *sql, int *eaten) +{ + int i = 1; /* starting at 1 skips the colon */ + char *name; + + /* keep this logic in sync with valid_variable_name() */ + if (IS_HIGHBIT_SET(sql[i]) || + strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" + "_", sql[i]) != NULL) + i++; + else + return NULL; + + while (IS_HIGHBIT_SET(sql[i]) || + strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" + "_0123456789", sql[i]) != NULL) + i++; + + name = pg_malloc(i); + memcpy(name, &sql[1], i - 1); + name[i - 1] = '\0'; + + *eaten = i; + return name; +} + +static char * +replaceVariable(char **sql, char *param, int len, char *value) +{ + int valueln = strlen(value); + + if (valueln > len) + { + size_t offset = param - *sql; + + *sql = pg_realloc(*sql, strlen(*sql) - len + valueln + 1); + param = *sql + offset; + } + + if (valueln != len) + memmove(param + valueln, param + len, strlen(param + len) + 1); + memcpy(param, value, valueln); + + return param + valueln; +} + +static char * +assignVariables(Variables *variables, char *sql) +{ + char *p, + *name, + *val; + + p = sql; + while ((p = strchr(p, ':')) != NULL) + { + int eaten; + + name = parseVariable(p, &eaten); + if (name == NULL) + { + while (*p == ':') + { + p++; + } + continue; + } + + val = getVariable(variables, name); + free(name); + if (val == NULL) + { + p++; + continue; + } + + p = replaceVariable(&sql, p, eaten, val); + } + + return sql; +} + +static void +getQueryParams(Variables *variables, const Command *command, + const char **params) +{ + int i; + + for (i = 0; i < command->argc - 1; i++) + params[i] = getVariable(variables, 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 */ + { + pg_log_error("cannot coerce %s to boolean", valueTypeName(pval)); + *bval = false; /* suppress uninitialized-variable warnings */ + 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) +{ + if (pval->type == PGBT_INT) + { + *ival = pval->u.ival; + return true; + } + else if (pval->type == PGBT_DOUBLE) + { + double dval = rint(pval->u.dval); + + if (isnan(dval) || !FLOAT8_FITS_IN_INT64(dval)) + { + pg_log_error("double to int overflow for %f", dval); + return false; + } + *ival = (int64) dval; + return true; + } + else /* BOOLEAN or NULL */ + { + pg_log_error("cannot coerce %s to int", valueTypeName(pval)); + return false; + } +} + +/* get a value as a double, or tell if there is a problem */ +static bool +coerceToDouble(PgBenchValue *pval, double *dval) +{ + if (pval->type == PGBT_DOUBLE) + { + *dval = pval->u.dval; + return true; + } + else if (pval->type == PGBT_INT) + { + *dval = (double) pval->u.ival; + return true; + } + else /* BOOLEAN or NULL */ + { + pg_log_error("cannot coerce %s to double", 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 +setIntValue(PgBenchValue *pv, int64 ival) +{ + pv->type = PGBT_INT; + pv->u.ival = ival; +} + +/* assign a double value */ +static void +setDoubleValue(PgBenchValue *pv, double dval) +{ + pv->type = PGBT_DOUBLE; + 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(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(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(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(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(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(st, args->expr, retval); + + /* no, another when, proceed */ + return evalLazyFunc(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 standard functions, + * which do not require lazy evaluation. + */ +static bool +evalStandardFunc(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(st, l->expr, &vargs[nargs])) + return false; + has_null |= vargs[nargs].type == PGBT_NULL; + } + + if (l != NULL) + { + pg_log_error("too many function arguments, maximum is %d", MAX_FARGS); + return false; + } + + /* NULL arguments */ + if (has_null && func != PGBENCH_IS && func != PGBENCH_DEBUG) + { + setNullValue(retval); + return true; + } + + /* then evaluate function */ + switch (func) + { + /* overloaded operators */ + case PGBENCH_ADD: + case PGBENCH_SUB: + 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]; + + Assert(nargs == 2); + + /* overloaded type management, double if some double */ + if ((lval->type == PGBT_DOUBLE || + rval->type == PGBT_DOUBLE) && func != PGBENCH_MOD) + { + double ld, + rd; + + if (!coerceToDouble(lval, &ld) || + !coerceToDouble(rval, &rd)) + return false; + + switch (func) + { + case PGBENCH_ADD: + setDoubleValue(retval, ld + rd); + return true; + + case PGBENCH_SUB: + setDoubleValue(retval, ld - rd); + return true; + + case PGBENCH_MUL: + setDoubleValue(retval, ld * rd); + return true; + + case PGBENCH_DIV: + 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); + } + } + else /* we have integer operands, or % */ + { + int64 li, + ri, + res; + + if (!coerceToInt(lval, &li) || + !coerceToInt(rval, &ri)) + return false; + + switch (func) + { + case PGBENCH_ADD: + if (pg_add_s64_overflow(li, ri, &res)) + { + pg_log_error("bigint add out of range"); + return false; + } + setIntValue(retval, res); + return true; + + case PGBENCH_SUB: + if (pg_sub_s64_overflow(li, ri, &res)) + { + pg_log_error("bigint sub out of range"); + return false; + } + setIntValue(retval, res); + return true; + + case PGBENCH_MUL: + if (pg_mul_s64_overflow(li, ri, &res)) + { + pg_log_error("bigint mul out of range"); + return false; + } + setIntValue(retval, res); + 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) + { + pg_log_error("division by zero"); + return false; + } + /* special handling of -1 divisor */ + if (ri == -1) + { + if (func == PGBENCH_DIV) + { + /* overflow check (needed for INT64_MIN) */ + if (li == PG_INT64_MIN) + { + pg_log_error("bigint div out of range"); + return false; + } + else + setIntValue(retval, -li); + } + else + setIntValue(retval, 0); + return true; + } + /* else divisor is not -1 */ + if (func == PGBENCH_DIV) + setIntValue(retval, li / ri); + else /* func == PGBENCH_MOD */ + setIntValue(retval, li % ri); + + return true; + + default: + /* cannot get here */ + Assert(0); + } + } + + Assert(0); + return false; /* NOTREACHED */ + } + + /* 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); + return true; + + /* 1 overloaded argument */ + case PGBENCH_ABS: + { + PgBenchValue *varg = &vargs[0]; + + Assert(nargs == 1); + + if (varg->type == PGBT_INT) + { + int64 i = varg->u.ival; + + setIntValue(retval, i < 0 ? -i : i); + } + else + { + double d = varg->u.dval; + + Assert(varg->type == PGBT_DOUBLE); + setDoubleValue(retval, d < 0.0 ? -d : d); + } + + return true; + } + + case PGBENCH_DEBUG: + { + PgBenchValue *varg = &vargs[0]; + + Assert(nargs == 1); + + fprintf(stderr, "debug(script=%d,command=%d): ", + st->use_file, st->command + 1); + + 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 if (varg->type == PGBT_DOUBLE) + fprintf(stderr, "double %.*g\n", DBL_DIG, varg->u.dval); + else /* internal error, unexpected type */ + Assert(0); + + *retval = *varg; + + return true; + } + + /* 1 double argument */ + case PGBENCH_DOUBLE: + case PGBENCH_SQRT: + case PGBENCH_LN: + case PGBENCH_EXP: + { + double dval; + + Assert(nargs == 1); + + if (!coerceToDouble(&vargs[0], &dval)) + return false; + + 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; + } + + /* 1 int argument */ + case PGBENCH_INT: + { + int64 ival; + + Assert(nargs == 1); + + if (!coerceToInt(&vargs[0], &ival)) + return false; + + setIntValue(retval, ival); + return true; + } + + /* variable number of arguments */ + case PGBENCH_LEAST: + case PGBENCH_GREATEST: + { + bool havedouble; + int i; + + Assert(nargs >= 1); + + /* need double result if any input is double */ + havedouble = false; + for (i = 0; i < nargs; i++) + { + if (vargs[i].type == PGBT_DOUBLE) + { + havedouble = true; + break; + } + } + if (havedouble) + { + double extremum; + + if (!coerceToDouble(&vargs[0], &extremum)) + return false; + for (i = 1; i < nargs; i++) + { + double dval; + + if (!coerceToDouble(&vargs[i], &dval)) + return false; + if (func == PGBENCH_LEAST) + extremum = Min(extremum, dval); + else + extremum = Max(extremum, dval); + } + setDoubleValue(retval, extremum); + } + else + { + int64 extremum; + + if (!coerceToInt(&vargs[0], &extremum)) + return false; + for (i = 1; i < nargs; i++) + { + int64 ival; + + if (!coerceToInt(&vargs[i], &ival)) + return false; + if (func == PGBENCH_LEAST) + extremum = Min(extremum, ival); + else + extremum = Max(extremum, ival); + } + setIntValue(retval, extremum); + } + return true; + } + + /* random functions */ + case PGBENCH_RANDOM: + case PGBENCH_RANDOM_EXPONENTIAL: + case PGBENCH_RANDOM_GAUSSIAN: + case PGBENCH_RANDOM_ZIPFIAN: + { + int64 imin, + imax, + delta; + + Assert(nargs >= 2); + + if (!coerceToInt(&vargs[0], &imin) || + !coerceToInt(&vargs[1], &imax)) + return false; + + /* check random range */ + if (unlikely(imin > imax)) + { + pg_log_error("empty range given to random"); + return false; + } + else if (unlikely(pg_sub_s64_overflow(imax, imin, &delta) || + pg_add_s64_overflow(delta, 1, &delta))) + { + /* prevent int overflows in random functions */ + pg_log_error("random range is too large"); + return false; + } + + if (func == PGBENCH_RANDOM) + { + Assert(nargs == 2); + setIntValue(retval, getrand(&st->cs_func_rs, imin, imax)); + } + else /* gaussian & exponential */ + { + double param; + + Assert(nargs == 3); + + if (!coerceToDouble(&vargs[2], ¶m)) + return false; + + if (func == PGBENCH_RANDOM_GAUSSIAN) + { + if (param < MIN_GAUSSIAN_PARAM) + { + pg_log_error("gaussian parameter must be at least %f (not %f)", + MIN_GAUSSIAN_PARAM, param); + return false; + } + + setIntValue(retval, + getGaussianRand(&st->cs_func_rs, + imin, imax, param)); + } + else if (func == PGBENCH_RANDOM_ZIPFIAN) + { + if (param < MIN_ZIPFIAN_PARAM || param > MAX_ZIPFIAN_PARAM) + { + pg_log_error("zipfian parameter must be in range [%.3f, %.0f] (not %f)", + MIN_ZIPFIAN_PARAM, MAX_ZIPFIAN_PARAM, param); + return false; + } + + setIntValue(retval, + getZipfianRand(&st->cs_func_rs, imin, imax, param)); + } + else /* exponential */ + { + if (param <= 0.0) + { + pg_log_error("exponential parameter must be greater than zero (not %f)", + param); + return false; + } + + setIntValue(retval, + getExponentialRand(&st->cs_func_rs, + imin, imax, param)); + } + } + + return true; + } + + case PGBENCH_POW: + { + PgBenchValue *lval = &vargs[0]; + PgBenchValue *rval = &vargs[1]; + double ld, + rd; + + Assert(nargs == 2); + + if (!coerceToDouble(lval, &ld) || + !coerceToDouble(rval, &rd)) + return false; + + setDoubleValue(retval, pow(ld, rd)); + + 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; + } + + /* hashing */ + case PGBENCH_HASH_FNV1A: + case PGBENCH_HASH_MURMUR2: + { + int64 val, + seed; + + Assert(nargs == 2); + + if (!coerceToInt(&vargs[0], &val) || + !coerceToInt(&vargs[1], &seed)) + return false; + + if (func == PGBENCH_HASH_MURMUR2) + setIntValue(retval, getHashMurmur2(val, seed)); + else if (func == PGBENCH_HASH_FNV1A) + setIntValue(retval, getHashFnv1a(val, seed)); + else + /* cannot get here */ + Assert(0); + + return true; + } + + case PGBENCH_PERMUTE: + { + int64 val, + size, + seed; + + Assert(nargs == 3); + + if (!coerceToInt(&vargs[0], &val) || + !coerceToInt(&vargs[1], &size) || + !coerceToInt(&vargs[2], &seed)) + return false; + + if (size <= 0) + { + pg_log_error("permute size parameter must be greater than zero"); + return false; + } + + setIntValue(retval, permute(val, size, seed)); + return true; + } + + default: + /* cannot get here */ + Assert(0); + /* dead code to avoid a compiler warning */ + return false; + } +} + +/* evaluate some function */ +static bool +evalFunc(CState *st, + PgBenchFunction func, PgBenchExprLink *args, PgBenchValue *retval) +{ + if (isLazyFunc(func)) + return evalLazyFunc(st, func, args, retval); + else + return evalStandardFunc(st, func, args, retval); +} + +/* + * Recursive evaluation of an expression in a pgbench script + * using the current state of variables. + * Returns whether the evaluation was ok, + * the value itself is returned through the retval pointer. + */ +static bool +evaluateExpr(CState *st, PgBenchExpr *expr, PgBenchValue *retval) +{ + switch (expr->etype) + { + case ENODE_CONSTANT: + { + *retval = expr->u.constant; + return true; + } + + case ENODE_VARIABLE: + { + Variable *var; + + if ((var = lookupVariable(&st->variables, expr->u.variable.varname)) == NULL) + { + pg_log_error("undefined variable \"%s\"", expr->u.variable.varname); + return false; + } + + if (!makeVariableValue(var)) + return false; + + *retval = var->value; + return true; + } + + case ENODE_FUNCTION: + return evalFunc(st, + expr->u.function.function, + expr->u.function.args, + retval); + + default: + /* internal error which should never occur */ + pg_fatal("unexpected enode type in evaluation: %d", expr->etype); + } +} + +/* + * Convert command name to meta-command enum identifier + */ +static MetaCommand +getMetaCommand(const char *cmd) +{ + MetaCommand mc; + + if (cmd == NULL) + mc = META_NONE; + else if (pg_strcasecmp(cmd, "set") == 0) + mc = META_SET; + else if (pg_strcasecmp(cmd, "setshell") == 0) + mc = META_SETSHELL; + else if (pg_strcasecmp(cmd, "shell") == 0) + mc = META_SHELL; + else if (pg_strcasecmp(cmd, "sleep") == 0) + mc = META_SLEEP; + else if (pg_strcasecmp(cmd, "if") == 0) + mc = META_IF; + else if (pg_strcasecmp(cmd, "elif") == 0) + mc = META_ELIF; + else if (pg_strcasecmp(cmd, "else") == 0) + mc = META_ELSE; + else if (pg_strcasecmp(cmd, "endif") == 0) + mc = META_ENDIF; + else if (pg_strcasecmp(cmd, "gset") == 0) + mc = META_GSET; + else if (pg_strcasecmp(cmd, "aset") == 0) + mc = META_ASET; + else if (pg_strcasecmp(cmd, "startpipeline") == 0) + mc = META_STARTPIPELINE; + else if (pg_strcasecmp(cmd, "endpipeline") == 0) + mc = META_ENDPIPELINE; + else + mc = META_NONE; + return mc; +} + +/* + * Run a shell command. The result is assigned to the variable if not NULL. + * Return true if succeeded, or false on error. + */ +static bool +runShellCommand(Variables *variables, char *variable, char **argv, int argc) +{ + char command[SHELL_COMMAND_SIZE]; + int i, + len = 0; + FILE *fp; + char res[64]; + char *endptr; + int retval; + + /*---------- + * Join arguments with whitespace separators. Arguments starting with + * exactly one colon are treated as variables: + * name - append a string "name" + * :var - append a variable named 'var' + * ::name - append a string ":name" + *---------- + */ + for (i = 0; i < argc; i++) + { + char *arg; + int arglen; + + if (argv[i][0] != ':') + { + arg = argv[i]; /* a string literal */ + } + else if (argv[i][1] == ':') + { + arg = argv[i] + 1; /* a string literal starting with colons */ + } + else if ((arg = getVariable(variables, argv[i] + 1)) == NULL) + { + pg_log_error("%s: undefined variable \"%s\"", argv[0], argv[i]); + return false; + } + + arglen = strlen(arg); + if (len + arglen + (i > 0 ? 1 : 0) >= SHELL_COMMAND_SIZE - 1) + { + pg_log_error("%s: shell command is too long", argv[0]); + return false; + } + + if (i > 0) + command[len++] = ' '; + memcpy(command + len, arg, arglen); + len += arglen; + } + + command[len] = '\0'; + + /* Fast path for non-assignment case */ + if (variable == NULL) + { + if (system(command)) + { + if (!timer_exceeded) + pg_log_error("%s: could not launch shell command", argv[0]); + return false; + } + return true; + } + + /* Execute the command with pipe and read the standard output. */ + if ((fp = popen(command, "r")) == NULL) + { + pg_log_error("%s: could not launch shell command", argv[0]); + return false; + } + if (fgets(res, sizeof(res), fp) == NULL) + { + if (!timer_exceeded) + pg_log_error("%s: could not read result of shell command", argv[0]); + (void) pclose(fp); + return false; + } + if (pclose(fp) < 0) + { + pg_log_error("%s: could not close shell command", argv[0]); + return false; + } + + /* Check whether the result is an integer and assign it to the variable */ + retval = (int) strtol(res, &endptr, 10); + while (*endptr != '\0' && isspace((unsigned char) *endptr)) + endptr++; + if (*res == '\0' || *endptr != '\0') + { + pg_log_error("%s: shell command must return an integer (not \"%s\")", argv[0], res); + return false; + } + if (!putVariableInt(variables, "setshell", variable, retval)) + return false; + + pg_log_debug("%s: shell parameter name: \"%s\", value: \"%s\"", argv[0], argv[1], res); + + return true; +} + +/* + * Report the abortion of the client when processing SQL commands. + */ +static void +commandFailed(CState *st, const char *cmd, const char *message) +{ + pg_log_error("client %d aborted in command %d (%s) of script %d; %s", + st->id, st->command, cmd, st->use_file, message); +} + +/* + * Report the error in the command while the script is executing. + */ +static void +commandError(CState *st, const char *message) +{ + Assert(sql_script[st->use_file].commands[st->command]->type == SQL_COMMAND); + pg_log_info("client %d got an error in command %d (SQL) of script %d; %s", + st->id, st->command, st->use_file, message); +} + +/* return a script number with a weighted choice. */ +static int +chooseScript(TState *thread) +{ + int i = 0; + int64 w; + + if (num_scripts == 1) + return 0; + + w = getrand(&thread->ts_choose_rs, 0, total_weight - 1); + do + { + w -= sql_script[i++].weight; + } while (w >= 0); + + return i - 1; +} + +/* + * Allocate space for CState->prepared: we need one boolean for each command + * of each script. + */ +static void +allocCStatePrepared(CState *st) +{ + Assert(st->prepared == NULL); + + st->prepared = pg_malloc(sizeof(bool *) * num_scripts); + for (int i = 0; i < num_scripts; i++) + { + ParsedScript *script = &sql_script[i]; + int numcmds; + + for (numcmds = 0; script->commands[numcmds] != NULL; numcmds++) + ; + st->prepared[i] = pg_malloc0(sizeof(bool) * numcmds); + } +} + +/* + * Prepare the SQL command from st->use_file at command_num. + */ +static void +prepareCommand(CState *st, int command_num) +{ + Command *command = sql_script[st->use_file].commands[command_num]; + + /* No prepare for non-SQL commands */ + if (command->type != SQL_COMMAND) + return; + + if (!st->prepared) + allocCStatePrepared(st); + + if (!st->prepared[st->use_file][command_num]) + { + PGresult *res; + + pg_log_debug("client %d preparing %s", st->id, command->prepname); + res = PQprepare(st->con, command->prepname, + command->argv[0], command->argc - 1, NULL); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_log_error("%s", PQerrorMessage(st->con)); + PQclear(res); + st->prepared[st->use_file][command_num] = true; + } +} + +/* + * Prepare all the commands in the script that come after the \startpipeline + * that's at position st->command, and the first \endpipeline we find. + * + * This sets the ->prepared flag for each relevant command as well as the + * \startpipeline itself, but doesn't move the st->command counter. + */ +static void +prepareCommandsInPipeline(CState *st) +{ + int j; + Command **commands = sql_script[st->use_file].commands; + + Assert(commands[st->command]->type == META_COMMAND && + commands[st->command]->meta == META_STARTPIPELINE); + + if (!st->prepared) + allocCStatePrepared(st); + + /* + * We set the 'prepared' flag on the \startpipeline itself to flag that we + * don't need to do this next time without calling prepareCommand(), even + * though we don't actually prepare this command. + */ + if (st->prepared[st->use_file][st->command]) + return; + + for (j = st->command + 1; commands[j] != NULL; j++) + { + if (commands[j]->type == META_COMMAND && + commands[j]->meta == META_ENDPIPELINE) + break; + + prepareCommand(st, j); + } + + st->prepared[st->use_file][st->command] = true; +} + +/* Send a SQL command, using the chosen querymode */ +static bool +sendCommand(CState *st, Command *command) +{ + int r; + + if (querymode == QUERY_SIMPLE) + { + char *sql; + + sql = pg_strdup(command->argv[0]); + sql = assignVariables(&st->variables, sql); + + pg_log_debug("client %d sending %s", st->id, sql); + r = PQsendQuery(st->con, sql); + free(sql); + } + else if (querymode == QUERY_EXTENDED) + { + const char *sql = command->argv[0]; + const char *params[MAX_ARGS]; + + getQueryParams(&st->variables, command, params); + + pg_log_debug("client %d sending %s", st->id, sql); + r = PQsendQueryParams(st->con, sql, command->argc - 1, + NULL, params, NULL, NULL, 0); + } + else if (querymode == QUERY_PREPARED) + { + const char *params[MAX_ARGS]; + + prepareCommand(st, st->command); + getQueryParams(&st->variables, command, params); + + pg_log_debug("client %d sending %s", st->id, command->prepname); + r = PQsendQueryPrepared(st->con, command->prepname, command->argc - 1, + params, NULL, NULL, 0); + } + else /* unknown sql mode */ + r = 0; + + if (r == 0) + { + pg_log_debug("client %d could not send %s", st->id, command->argv[0]); + return false; + } + else + return true; +} + +/* + * Get the error status from the error code. + */ +static EStatus +getSQLErrorStatus(const char *sqlState) +{ + if (sqlState != NULL) + { + if (strcmp(sqlState, ERRCODE_T_R_SERIALIZATION_FAILURE) == 0) + return ESTATUS_SERIALIZATION_ERROR; + else if (strcmp(sqlState, ERRCODE_T_R_DEADLOCK_DETECTED) == 0) + return ESTATUS_DEADLOCK_ERROR; + } + + return ESTATUS_OTHER_SQL_ERROR; +} + +/* + * Returns true if this type of error can be retried. + */ +static bool +canRetryError(EStatus estatus) +{ + return (estatus == ESTATUS_SERIALIZATION_ERROR || + estatus == ESTATUS_DEADLOCK_ERROR); +} + +/* + * Process query response from the backend. + * + * If varprefix is not NULL, it's the variable name prefix where to store + * the results of the *last* command (META_GSET) or *all* commands + * (META_ASET). + * + * Returns true if everything is A-OK, false if any error occurs. + */ +static bool +readCommandResponse(CState *st, MetaCommand meta, char *varprefix) +{ + PGresult *res; + PGresult *next_res; + int qrynum = 0; + + /* + * varprefix should be set only with \gset or \aset, and \endpipeline and + * SQL commands do not need it. + */ + Assert((meta == META_NONE && varprefix == NULL) || + ((meta == META_ENDPIPELINE) && varprefix == NULL) || + ((meta == META_GSET || meta == META_ASET) && varprefix != NULL)); + + res = PQgetResult(st->con); + + while (res != NULL) + { + bool is_last; + + /* peek at the next result to know whether the current is last */ + next_res = PQgetResult(st->con); + is_last = (next_res == NULL); + + switch (PQresultStatus(res)) + { + case PGRES_COMMAND_OK: /* non-SELECT commands */ + case PGRES_EMPTY_QUERY: /* may be used for testing no-op overhead */ + if (is_last && meta == META_GSET) + { + pg_log_error("client %d script %d command %d query %d: expected one row, got %d", + st->id, st->use_file, st->command, qrynum, 0); + st->estatus = ESTATUS_META_COMMAND_ERROR; + goto error; + } + break; + + case PGRES_TUPLES_OK: + if ((is_last && meta == META_GSET) || meta == META_ASET) + { + int ntuples = PQntuples(res); + + if (meta == META_GSET && ntuples != 1) + { + /* under \gset, report the error */ + pg_log_error("client %d script %d command %d query %d: expected one row, got %d", + st->id, st->use_file, st->command, qrynum, PQntuples(res)); + st->estatus = ESTATUS_META_COMMAND_ERROR; + goto error; + } + else if (meta == META_ASET && ntuples <= 0) + { + /* coldly skip empty result under \aset */ + break; + } + + /* store results into variables */ + for (int fld = 0; fld < PQnfields(res); fld++) + { + char *varname = PQfname(res, fld); + + /* allocate varname only if necessary, freed below */ + if (*varprefix != '\0') + varname = psprintf("%s%s", varprefix, varname); + + /* store last row result as a string */ + if (!putVariable(&st->variables, meta == META_ASET ? "aset" : "gset", varname, + PQgetvalue(res, ntuples - 1, fld))) + { + /* internal error */ + pg_log_error("client %d script %d command %d query %d: error storing into variable %s", + st->id, st->use_file, st->command, qrynum, varname); + st->estatus = ESTATUS_META_COMMAND_ERROR; + goto error; + } + + if (*varprefix != '\0') + pg_free(varname); + } + } + /* otherwise the result is simply thrown away by PQclear below */ + break; + + case PGRES_PIPELINE_SYNC: + pg_log_debug("client %d pipeline ending", st->id); + if (PQexitPipelineMode(st->con) != 1) + pg_log_error("client %d failed to exit pipeline mode: %s", st->id, + PQerrorMessage(st->con)); + break; + + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + st->estatus = getSQLErrorStatus(PQresultErrorField(res, + PG_DIAG_SQLSTATE)); + if (canRetryError(st->estatus)) + { + if (verbose_errors) + commandError(st, PQerrorMessage(st->con)); + goto error; + } + /* fall through */ + + default: + /* anything else is unexpected */ + pg_log_error("client %d script %d aborted in command %d query %d: %s", + st->id, st->use_file, st->command, qrynum, + PQerrorMessage(st->con)); + goto error; + } + + PQclear(res); + qrynum++; + res = next_res; + } + + if (qrynum == 0) + { + pg_log_error("client %d command %d: no results", st->id, st->command); + return false; + } + + return true; + +error: + PQclear(res); + PQclear(next_res); + do + { + res = PQgetResult(st->con); + PQclear(res); + } while (res); + + return false; +} + +/* + * Parse the argument to a \sleep command, and return the requested amount + * of delay, in microseconds. Returns true on success, false on error. + */ +static bool +evaluateSleep(Variables *variables, int argc, char **argv, int *usecs) +{ + char *var; + int usec; + + if (*argv[1] == ':') + { + if ((var = getVariable(variables, argv[1] + 1)) == NULL) + { + pg_log_error("%s: undefined variable \"%s\"", argv[0], argv[1] + 1); + return false; + } + + usec = atoi(var); + + /* Raise an error if the value of a variable is not a number */ + if (usec == 0 && !isdigit((unsigned char) *var)) + { + pg_log_error("%s: invalid sleep time \"%s\" for variable \"%s\"", + argv[0], var, argv[1] + 1); + return false; + } + } + else + usec = atoi(argv[1]); + + if (argc > 2) + { + if (pg_strcasecmp(argv[2], "ms") == 0) + usec *= 1000; + else if (pg_strcasecmp(argv[2], "s") == 0) + usec *= 1000000; + } + else + usec *= 1000000; + + *usecs = usec; + return true; +} + + +/* + * Returns true if the error can be retried. + */ +static bool +doRetry(CState *st, pg_time_usec_t *now) +{ + Assert(st->estatus != ESTATUS_NO_ERROR); + + /* We can only retry serialization or deadlock errors. */ + if (!canRetryError(st->estatus)) + return false; + + /* + * We must have at least one option to limit the retrying of transactions + * that got an error. + */ + Assert(max_tries || latency_limit || duration > 0); + + /* + * We cannot retry the error if we have reached the maximum number of + * tries. + */ + if (max_tries && st->tries >= max_tries) + return false; + + /* + * We cannot retry the error if we spent too much time on this + * transaction. + */ + if (latency_limit) + { + pg_time_now_lazy(now); + if (*now - st->txn_scheduled > latency_limit) + return false; + } + + /* + * We cannot retry the error if the benchmark duration is over. + */ + if (timer_exceeded) + return false; + + /* OK */ + return true; +} + +/* + * Read results and discard it until a sync point. + */ +static int +discardUntilSync(CState *st) +{ + /* send a sync */ + if (!PQpipelineSync(st->con)) + { + pg_log_error("client %d aborted: failed to send a pipeline sync", + st->id); + return 0; + } + + /* receive PGRES_PIPELINE_SYNC and null following it */ + for (;;) + { + PGresult *res = PQgetResult(st->con); + + if (PQresultStatus(res) == PGRES_PIPELINE_SYNC) + { + PQclear(res); + res = PQgetResult(st->con); + Assert(res == NULL); + break; + } + PQclear(res); + } + + /* exit pipeline */ + if (PQexitPipelineMode(st->con) != 1) + { + pg_log_error("client %d aborted: failed to exit pipeline mode for rolling back the failed transaction", + st->id); + return 0; + } + return 1; +} + +/* + * Get the transaction status at the end of a command especially for + * checking if we are in a (failed) transaction block. + */ +static TStatus +getTransactionStatus(PGconn *con) +{ + PGTransactionStatusType tx_status; + + tx_status = PQtransactionStatus(con); + switch (tx_status) + { + case PQTRANS_IDLE: + return TSTATUS_IDLE; + case PQTRANS_INTRANS: + case PQTRANS_INERROR: + return TSTATUS_IN_BLOCK; + case PQTRANS_UNKNOWN: + /* PQTRANS_UNKNOWN is expected given a broken connection */ + if (PQstatus(con) == CONNECTION_BAD) + return TSTATUS_CONN_ERROR; + /* fall through */ + case PQTRANS_ACTIVE: + default: + + /* + * We cannot find out whether we are in a transaction block or + * not. Internal error which should never occur. + */ + pg_log_error("unexpected transaction status %d", tx_status); + return TSTATUS_OTHER_ERROR; + } + + /* not reached */ + Assert(false); + return TSTATUS_OTHER_ERROR; +} + +/* + * Print verbose messages of an error + */ +static void +printVerboseErrorMessages(CState *st, pg_time_usec_t *now, bool is_retry) +{ + static PQExpBuffer buf = NULL; + + if (buf == NULL) + buf = createPQExpBuffer(); + else + resetPQExpBuffer(buf); + + printfPQExpBuffer(buf, "client %d ", st->id); + appendPQExpBuffer(buf, "%s", + (is_retry ? + "repeats the transaction after the error" : + "ends the failed transaction")); + appendPQExpBuffer(buf, " (try %u", st->tries); + + /* Print max_tries if it is not unlimitted. */ + if (max_tries) + appendPQExpBuffer(buf, "/%u", max_tries); + + /* + * If the latency limit is used, print a percentage of the current + * transaction latency from the latency limit. + */ + if (latency_limit) + { + pg_time_now_lazy(now); + appendPQExpBuffer(buf, ", %.3f%% of the maximum time of tries was used", + (100.0 * (*now - st->txn_scheduled) / latency_limit)); + } + appendPQExpBuffer(buf, ")\n"); + + pg_log_info("%s", buf->data); +} + +/* + * Advance the state machine of a connection. + */ +static void +advanceConnectionState(TState *thread, CState *st, StatsData *agg) +{ + + /* + * gettimeofday() isn't free, so we get the current timestamp lazily the + * first time it's needed, and reuse the same value throughout this + * function after that. This also ensures that e.g. the calculated + * latency reported in the log file and in the totals are the same. Zero + * means "not set yet". Reset "now" when we execute shell commands or + * expressions, which might take a non-negligible amount of time, though. + */ + pg_time_usec_t now = 0; + + /* + * Loop in the state machine, until we have to wait for a result from the + * server or have to sleep for throttling or \sleep. + * + * Note: In the switch-statement below, 'break' will loop back here, + * meaning "continue in the state machine". Return is used to return to + * the caller, giving the thread the opportunity to advance another + * client. + */ + for (;;) + { + Command *command; + + switch (st->state) + { + /* Select transaction (script) to run. */ + case CSTATE_CHOOSE_SCRIPT: + st->use_file = chooseScript(thread); + Assert(conditional_stack_empty(st->cstack)); + + /* reset transaction variables to default values */ + st->estatus = ESTATUS_NO_ERROR; + st->tries = 1; + + pg_log_debug("client %d executing script \"%s\"", + st->id, sql_script[st->use_file].desc); + + /* + * If time is over, we're done; otherwise, get ready to start + * a new transaction, or to get throttled if that's requested. + */ + st->state = timer_exceeded ? CSTATE_FINISHED : + throttle_delay > 0 ? CSTATE_PREPARE_THROTTLE : CSTATE_START_TX; + break; + + /* Start new transaction (script) */ + case CSTATE_START_TX: + pg_time_now_lazy(&now); + + /* establish connection if needed, i.e. under --connect */ + if (st->con == NULL) + { + pg_time_usec_t start = now; + + if ((st->con = doConnect()) == NULL) + { + /* + * as the bench is already running, we do not abort + * the process + */ + pg_log_error("client %d aborted while establishing connection", st->id); + st->state = CSTATE_ABORTED; + break; + } + + /* reset now after connection */ + now = pg_time_now(); + + thread->conn_duration += now - start; + + /* Reset session-local state */ + pg_free(st->prepared); + st->prepared = NULL; + } + + /* + * It is the first try to run this transaction. Remember the + * random state: maybe it will get an error and we will need + * to run it again. + */ + st->random_state = st->cs_func_rs; + + /* record transaction start time */ + st->txn_begin = now; + + /* + * When not throttling, this is also the transaction's + * scheduled start time. + */ + if (!throttle_delay) + st->txn_scheduled = now; + + /* Begin with the first command */ + st->state = CSTATE_START_COMMAND; + st->command = 0; + break; + + /* + * Handle throttling once per transaction by sleeping. + */ + case CSTATE_PREPARE_THROTTLE: + + /* + * Generate a delay such that the series of delays will + * approximate a Poisson distribution centered on the + * throttle_delay time. + * + * If transactions are too slow or a given wait is shorter + * than a transaction, the next transaction will start right + * away. + */ + Assert(throttle_delay > 0); + + thread->throttle_trigger += + getPoissonRand(&thread->ts_throttle_rs, throttle_delay); + st->txn_scheduled = thread->throttle_trigger; + + /* + * If --latency-limit is used, and this slot is already late + * so that the transaction will miss the latency limit even if + * it completed immediately, skip this time slot and loop to + * reschedule. + */ + if (latency_limit) + { + pg_time_now_lazy(&now); + + if (thread->throttle_trigger < now - latency_limit) + { + processXactStats(thread, st, &now, true, agg); + + /* + * Finish client if -T or -t was exceeded. + * + * Stop counting skipped transactions under -T as soon + * as the timer is exceeded. Because otherwise it can + * take a very long time to count all of them + * especially when quite a lot of them happen with + * unrealistically high rate setting in -R, which + * would prevent pgbench from ending immediately. + * Because of this behavior, note that there is no + * guarantee that all skipped transactions are counted + * under -T though there is under -t. This is OK in + * practice because it's very unlikely to happen with + * realistic setting. + */ + if (timer_exceeded || (nxacts > 0 && st->cnt >= nxacts)) + st->state = CSTATE_FINISHED; + + /* Go back to top of loop with CSTATE_PREPARE_THROTTLE */ + break; + } + } + + /* + * stop client if next transaction is beyond pgbench end of + * execution; otherwise, throttle it. + */ + st->state = end_time > 0 && st->txn_scheduled > end_time ? + CSTATE_FINISHED : CSTATE_THROTTLE; + break; + + /* + * Wait until it's time to start next transaction. + */ + case CSTATE_THROTTLE: + pg_time_now_lazy(&now); + + if (now < st->txn_scheduled) + return; /* still sleeping, nothing to do here */ + + /* done sleeping, but don't start transaction if we're done */ + st->state = timer_exceeded ? CSTATE_FINISHED : CSTATE_START_TX; + break; + + /* + * Send a command to server (or execute a meta-command) + */ + case CSTATE_START_COMMAND: + command = sql_script[st->use_file].commands[st->command]; + + /* Transition to script end processing if done */ + if (command == NULL) + { + st->state = CSTATE_END_TX; + break; + } + + /* record begin time of next command, and initiate it */ + if (report_per_command) + { + pg_time_now_lazy(&now); + st->stmt_begin = now; + } + + /* Execute the command */ + if (command->type == SQL_COMMAND) + { + /* disallow \aset and \gset in pipeline mode */ + if (PQpipelineStatus(st->con) != PQ_PIPELINE_OFF) + { + if (command->meta == META_GSET) + { + commandFailed(st, "gset", "\\gset is not allowed in pipeline mode"); + st->state = CSTATE_ABORTED; + break; + } + else if (command->meta == META_ASET) + { + commandFailed(st, "aset", "\\aset is not allowed in pipeline mode"); + st->state = CSTATE_ABORTED; + break; + } + } + + if (!sendCommand(st, command)) + { + commandFailed(st, "SQL", "SQL command send failed"); + st->state = CSTATE_ABORTED; + } + else + { + /* Wait for results, unless in pipeline mode */ + if (PQpipelineStatus(st->con) == PQ_PIPELINE_OFF) + st->state = CSTATE_WAIT_RESULT; + else + st->state = CSTATE_END_COMMAND; + } + } + else if (command->type == META_COMMAND) + { + /*----- + * Possible state changes when executing meta commands: + * - on errors CSTATE_ABORTED + * - on sleep CSTATE_SLEEP + * - else CSTATE_END_COMMAND + */ + st->state = executeMetaCommand(st, &now); + if (st->state == CSTATE_ABORTED) + st->estatus = ESTATUS_META_COMMAND_ERROR; + } + + /* + * We're now waiting for an SQL command to complete, or + * finished processing a metacommand, or need to sleep, or + * something bad happened. + */ + Assert(st->state == CSTATE_WAIT_RESULT || + st->state == CSTATE_END_COMMAND || + st->state == CSTATE_SLEEP || + st->state == CSTATE_ABORTED); + break; + + /* + * non executed conditional branch + */ + case CSTATE_SKIP_COMMAND: + Assert(!conditional_active(st->cstack)); + /* quickly skip commands until something to do... */ + while (true) + { + Command *command; + + command = sql_script[st->use_file].commands[st->command]; + + /* cannot reach end of script in that state */ + Assert(command != NULL); + + /* + * if this is conditional related, update conditional + * state + */ + if (command->type == META_COMMAND && + (command->meta == META_IF || + command->meta == META_ELIF || + command->meta == META_ELSE || + command->meta == META_ENDIF)) + { + switch (conditional_stack_peek(st->cstack)) + { + case IFSTATE_FALSE: + if (command->meta == META_IF || + command->meta == META_ELIF) + { + /* we must evaluate the condition */ + st->state = CSTATE_START_COMMAND; + } + else if (command->meta == META_ELSE) + { + /* we must execute next command */ + conditional_stack_poke(st->cstack, + IFSTATE_ELSE_TRUE); + st->state = CSTATE_START_COMMAND; + st->command++; + } + else if (command->meta == META_ENDIF) + { + Assert(!conditional_stack_empty(st->cstack)); + conditional_stack_pop(st->cstack); + if (conditional_active(st->cstack)) + st->state = CSTATE_START_COMMAND; + + /* + * else state remains in + * CSTATE_SKIP_COMMAND + */ + st->command++; + } + break; + + case IFSTATE_IGNORED: + case IFSTATE_ELSE_FALSE: + if (command->meta == META_IF) + conditional_stack_push(st->cstack, + IFSTATE_IGNORED); + else if (command->meta == META_ENDIF) + { + Assert(!conditional_stack_empty(st->cstack)); + conditional_stack_pop(st->cstack); + if (conditional_active(st->cstack)) + st->state = CSTATE_START_COMMAND; + } + /* could detect "else" & "elif" after "else" */ + st->command++; + break; + + case IFSTATE_NONE: + case IFSTATE_TRUE: + case IFSTATE_ELSE_TRUE: + default: + + /* + * inconsistent if inactive, unreachable dead + * code + */ + Assert(false); + } + } + else + { + /* skip and consider next */ + st->command++; + } + + if (st->state != CSTATE_SKIP_COMMAND) + /* out of quick skip command loop */ + break; + } + break; + + /* + * Wait for the current SQL command to complete + */ + case CSTATE_WAIT_RESULT: + pg_log_debug("client %d receiving", st->id); + + /* + * Only check for new network data if we processed all data + * fetched prior. Otherwise we end up doing a syscall for each + * individual pipelined query, which has a measurable + * performance impact. + */ + if (PQisBusy(st->con) && !PQconsumeInput(st->con)) + { + /* there's something wrong */ + commandFailed(st, "SQL", "perhaps the backend died while processing"); + st->state = CSTATE_ABORTED; + break; + } + if (PQisBusy(st->con)) + return; /* don't have the whole result yet */ + + /* store or discard the query results */ + if (readCommandResponse(st, + sql_script[st->use_file].commands[st->command]->meta, + sql_script[st->use_file].commands[st->command]->varprefix)) + { + /* + * outside of pipeline mode: stop reading results. + * pipeline mode: continue reading results until an + * end-of-pipeline response. + */ + if (PQpipelineStatus(st->con) != PQ_PIPELINE_ON) + st->state = CSTATE_END_COMMAND; + } + else if (canRetryError(st->estatus)) + st->state = CSTATE_ERROR; + else + st->state = CSTATE_ABORTED; + break; + + /* + * Wait until sleep is done. This state is entered after a + * \sleep metacommand. The behavior is similar to + * CSTATE_THROTTLE, but proceeds to CSTATE_START_COMMAND + * instead of CSTATE_START_TX. + */ + case CSTATE_SLEEP: + pg_time_now_lazy(&now); + if (now < st->sleep_until) + return; /* still sleeping, nothing to do here */ + /* Else done sleeping. */ + st->state = CSTATE_END_COMMAND; + break; + + /* + * End of command: record stats and proceed to next command. + */ + case CSTATE_END_COMMAND: + + /* + * command completed: accumulate per-command execution times + * in thread-local data structure, if per-command latencies + * are requested. + */ + if (report_per_command) + { + Command *command; + + pg_time_now_lazy(&now); + + command = sql_script[st->use_file].commands[st->command]; + /* XXX could use a mutex here, but we choose not to */ + addToSimpleStats(&command->stats, + PG_TIME_GET_DOUBLE(now - st->stmt_begin)); + } + + /* Go ahead with next command, to be executed or skipped */ + st->command++; + st->state = conditional_active(st->cstack) ? + CSTATE_START_COMMAND : CSTATE_SKIP_COMMAND; + break; + + /* + * Clean up after an error. + */ + case CSTATE_ERROR: + { + TStatus tstatus; + + Assert(st->estatus != ESTATUS_NO_ERROR); + + /* Clear the conditional stack */ + conditional_stack_reset(st->cstack); + + /* Read and discard until a sync point in pipeline mode */ + if (PQpipelineStatus(st->con) != PQ_PIPELINE_OFF) + { + if (!discardUntilSync(st)) + { + st->state = CSTATE_ABORTED; + break; + } + } + + /* + * Check if we have a (failed) transaction block or not, + * and roll it back if any. + */ + tstatus = getTransactionStatus(st->con); + if (tstatus == TSTATUS_IN_BLOCK) + { + /* Try to rollback a (failed) transaction block. */ + if (!PQsendQuery(st->con, "ROLLBACK")) + { + pg_log_error("client %d aborted: failed to send sql command for rolling back the failed transaction", + st->id); + st->state = CSTATE_ABORTED; + } + else + st->state = CSTATE_WAIT_ROLLBACK_RESULT; + } + else if (tstatus == TSTATUS_IDLE) + { + /* + * If time is over, we're done; otherwise, check if we + * can retry the error. + */ + st->state = timer_exceeded ? CSTATE_FINISHED : + doRetry(st, &now) ? CSTATE_RETRY : CSTATE_FAILURE; + } + else + { + if (tstatus == TSTATUS_CONN_ERROR) + pg_log_error("perhaps the backend died while processing"); + + pg_log_error("client %d aborted while receiving the transaction status", st->id); + st->state = CSTATE_ABORTED; + } + break; + } + + /* + * Wait for the rollback command to complete + */ + case CSTATE_WAIT_ROLLBACK_RESULT: + { + PGresult *res; + + pg_log_debug("client %d receiving", st->id); + if (!PQconsumeInput(st->con)) + { + pg_log_error("client %d aborted while rolling back the transaction after an error; perhaps the backend died while processing", + st->id); + st->state = CSTATE_ABORTED; + break; + } + if (PQisBusy(st->con)) + return; /* don't have the whole result yet */ + + /* + * Read and discard the query result; + */ + res = PQgetResult(st->con); + switch (PQresultStatus(res)) + { + case PGRES_COMMAND_OK: + /* OK */ + PQclear(res); + /* null must be returned */ + res = PQgetResult(st->con); + Assert(res == NULL); + + /* + * If time is over, we're done; otherwise, check + * if we can retry the error. + */ + st->state = timer_exceeded ? CSTATE_FINISHED : + doRetry(st, &now) ? CSTATE_RETRY : CSTATE_FAILURE; + break; + default: + pg_log_error("client %d aborted while rolling back the transaction after an error; %s", + st->id, PQerrorMessage(st->con)); + PQclear(res); + st->state = CSTATE_ABORTED; + break; + } + break; + } + + /* + * Retry the transaction after an error. + */ + case CSTATE_RETRY: + command = sql_script[st->use_file].commands[st->command]; + + /* + * Inform that the transaction will be retried after the + * error. + */ + if (verbose_errors) + printVerboseErrorMessages(st, &now, true); + + /* Count tries and retries */ + st->tries++; + command->retries++; + + /* + * Reset the random state as they were at the beginning of the + * transaction. + */ + st->cs_func_rs = st->random_state; + + /* Process the first transaction command. */ + st->command = 0; + st->estatus = ESTATUS_NO_ERROR; + st->state = CSTATE_START_COMMAND; + break; + + /* + * Record a failed transaction. + */ + case CSTATE_FAILURE: + command = sql_script[st->use_file].commands[st->command]; + + /* Accumulate the failure. */ + command->failures++; + + /* + * Inform that the failed transaction will not be retried. + */ + if (verbose_errors) + printVerboseErrorMessages(st, &now, false); + + /* End the failed transaction. */ + st->state = CSTATE_END_TX; + break; + + /* + * End of transaction (end of script, really). + */ + case CSTATE_END_TX: + { + TStatus tstatus; + + /* transaction finished: calculate latency and do log */ + processXactStats(thread, st, &now, false, agg); + + /* + * missing \endif... cannot happen if CheckConditional was + * okay + */ + Assert(conditional_stack_empty(st->cstack)); + + /* + * We must complete all the transaction blocks that were + * started in this script. + */ + tstatus = getTransactionStatus(st->con); + if (tstatus == TSTATUS_IN_BLOCK) + { + pg_log_error("client %d aborted: end of script reached without completing the last transaction", + st->id); + st->state = CSTATE_ABORTED; + break; + } + else if (tstatus != TSTATUS_IDLE) + { + if (tstatus == TSTATUS_CONN_ERROR) + pg_log_error("perhaps the backend died while processing"); + + pg_log_error("client %d aborted while receiving the transaction status", st->id); + st->state = CSTATE_ABORTED; + break; + } + + if (is_connect) + { + pg_time_usec_t start = now; + + pg_time_now_lazy(&start); + finishCon(st); + now = pg_time_now(); + thread->conn_duration += now - start; + } + + if ((st->cnt >= nxacts && duration <= 0) || timer_exceeded) + { + /* script completed */ + st->state = CSTATE_FINISHED; + break; + } + + /* next transaction (script) */ + st->state = CSTATE_CHOOSE_SCRIPT; + + /* + * Ensure that we always return on this point, so as to + * avoid an infinite loop if the script only contains meta + * commands. + */ + return; + } + + /* + * Final states. Close the connection if it's still open. + */ + case CSTATE_ABORTED: + case CSTATE_FINISHED: + + /* + * Don't measure the disconnection delays here even if in + * CSTATE_FINISHED and -C/--connect option is specified. + * Because in this case all the connections that this thread + * established are closed at the end of transactions and the + * disconnection delays should have already been measured at + * that moment. + * + * In CSTATE_ABORTED state, the measurement is no longer + * necessary because we cannot report complete results anyways + * in this case. + */ + finishCon(st); + return; + } + } +} + +/* + * Subroutine for advanceConnectionState -- initiate or execute the current + * meta command, and return the next state to set. + * + * *now is updated to the current time, unless the command is expected to + * take no time to execute. + */ +static ConnectionStateEnum +executeMetaCommand(CState *st, pg_time_usec_t *now) +{ + Command *command = sql_script[st->use_file].commands[st->command]; + int argc; + char **argv; + + Assert(command != NULL && command->type == META_COMMAND); + + argc = command->argc; + argv = command->argv; + + if (unlikely(__pg_log_level <= PG_LOG_DEBUG)) + { + PQExpBufferData buf; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, "client %d executing \\%s", st->id, argv[0]); + for (int i = 1; i < argc; i++) + appendPQExpBuffer(&buf, " %s", argv[i]); + + pg_log_debug("%s", buf.data); + + termPQExpBuffer(&buf); + } + + if (command->meta == META_SLEEP) + { + int usec; + + /* + * A \sleep doesn't execute anything, we just get the delay from the + * argument, and enter the CSTATE_SLEEP state. (The per-command + * latency will be recorded in CSTATE_SLEEP state, not here, after the + * delay has elapsed.) + */ + if (!evaluateSleep(&st->variables, argc, argv, &usec)) + { + commandFailed(st, "sleep", "execution of meta-command failed"); + return CSTATE_ABORTED; + } + + pg_time_now_lazy(now); + st->sleep_until = (*now) + usec; + return CSTATE_SLEEP; + } + else if (command->meta == META_SET) + { + PgBenchExpr *expr = command->expr; + PgBenchValue result; + + if (!evaluateExpr(st, expr, &result)) + { + commandFailed(st, argv[0], "evaluation of meta-command failed"); + return CSTATE_ABORTED; + } + + if (!putVariableValue(&st->variables, argv[0], argv[1], &result)) + { + commandFailed(st, "set", "assignment of meta-command failed"); + return CSTATE_ABORTED; + } + } + else if (command->meta == META_IF) + { + /* backslash commands with an expression to evaluate */ + PgBenchExpr *expr = command->expr; + PgBenchValue result; + bool cond; + + if (!evaluateExpr(st, expr, &result)) + { + commandFailed(st, argv[0], "evaluation of meta-command failed"); + return CSTATE_ABORTED; + } + + cond = valueTruth(&result); + conditional_stack_push(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE); + } + else if (command->meta == META_ELIF) + { + /* backslash commands with an expression to evaluate */ + PgBenchExpr *expr = command->expr; + PgBenchValue result; + bool cond; + + if (conditional_stack_peek(st->cstack) == IFSTATE_TRUE) + { + /* elif after executed block, skip eval and wait for endif. */ + conditional_stack_poke(st->cstack, IFSTATE_IGNORED); + return CSTATE_END_COMMAND; + } + + if (!evaluateExpr(st, expr, &result)) + { + commandFailed(st, argv[0], "evaluation of meta-command failed"); + return CSTATE_ABORTED; + } + + cond = valueTruth(&result); + Assert(conditional_stack_peek(st->cstack) == IFSTATE_FALSE); + conditional_stack_poke(st->cstack, cond ? IFSTATE_TRUE : IFSTATE_FALSE); + } + else if (command->meta == META_ELSE) + { + switch (conditional_stack_peek(st->cstack)) + { + case IFSTATE_TRUE: + conditional_stack_poke(st->cstack, IFSTATE_ELSE_FALSE); + break; + case IFSTATE_FALSE: /* inconsistent if active */ + case IFSTATE_IGNORED: /* inconsistent if active */ + case IFSTATE_NONE: /* else without if */ + case IFSTATE_ELSE_TRUE: /* else after else */ + case IFSTATE_ELSE_FALSE: /* else after else */ + default: + /* dead code if conditional check is ok */ + Assert(false); + } + } + else if (command->meta == META_ENDIF) + { + Assert(!conditional_stack_empty(st->cstack)); + conditional_stack_pop(st->cstack); + } + else if (command->meta == META_SETSHELL) + { + if (!runShellCommand(&st->variables, argv[1], argv + 2, argc - 2)) + { + commandFailed(st, "setshell", "execution of meta-command failed"); + return CSTATE_ABORTED; + } + } + else if (command->meta == META_SHELL) + { + if (!runShellCommand(&st->variables, NULL, argv + 1, argc - 1)) + { + commandFailed(st, "shell", "execution of meta-command failed"); + return CSTATE_ABORTED; + } + } + else if (command->meta == META_STARTPIPELINE) + { + /* + * In pipeline mode, we use a workflow based on libpq pipeline + * functions. + */ + if (querymode == QUERY_SIMPLE) + { + commandFailed(st, "startpipeline", "cannot use pipeline mode with the simple query protocol"); + return CSTATE_ABORTED; + } + + /* + * If we're in prepared-query mode, we need to prepare all the + * commands that are inside the pipeline before we actually start the + * pipeline itself. This solves the problem that running BEGIN + * ISOLATION LEVEL SERIALIZABLE in a pipeline would fail due to a + * snapshot having been acquired by the prepare within the pipeline. + */ + if (querymode == QUERY_PREPARED) + prepareCommandsInPipeline(st); + + if (PQpipelineStatus(st->con) != PQ_PIPELINE_OFF) + { + commandFailed(st, "startpipeline", "already in pipeline mode"); + return CSTATE_ABORTED; + } + if (PQenterPipelineMode(st->con) == 0) + { + commandFailed(st, "startpipeline", "failed to enter pipeline mode"); + return CSTATE_ABORTED; + } + } + else if (command->meta == META_ENDPIPELINE) + { + if (PQpipelineStatus(st->con) != PQ_PIPELINE_ON) + { + commandFailed(st, "endpipeline", "not in pipeline mode"); + return CSTATE_ABORTED; + } + if (!PQpipelineSync(st->con)) + { + commandFailed(st, "endpipeline", "failed to send a pipeline sync"); + return CSTATE_ABORTED; + } + /* Now wait for the PGRES_PIPELINE_SYNC and exit pipeline mode there */ + /* collect pending results before getting out of pipeline mode */ + return CSTATE_WAIT_RESULT; + } + + /* + * executing the expression or shell command might have taken a + * non-negligible amount of time, so reset 'now' + */ + *now = 0; + + return CSTATE_END_COMMAND; +} + +/* + * Return the number fo failed transactions. + */ +static int64 +getFailures(const StatsData *stats) +{ + return (stats->serialization_failures + + stats->deadlock_failures); +} + +/* + * Return a string constant representing the result of a transaction + * that is not successfully processed. + */ +static const char * +getResultString(bool skipped, EStatus estatus) +{ + if (skipped) + return "skipped"; + else if (failures_detailed) + { + switch (estatus) + { + case ESTATUS_SERIALIZATION_ERROR: + return "serialization"; + case ESTATUS_DEADLOCK_ERROR: + return "deadlock"; + default: + /* internal error which should never occur */ + pg_fatal("unexpected error status: %d", estatus); + } + } + else + return "failed"; +} + +/* + * Print log entry after completing one transaction. + * + * We print Unix-epoch timestamps in the log, so that entries can be + * correlated against other logs. + * + * XXX We could obtain the time from the caller and just shift it here, to + * avoid the cost of an extra call to pg_time_now(). + */ +static void +doLog(TState *thread, CState *st, + StatsData *agg, bool skipped, double latency, double lag) +{ + FILE *logfile = thread->logfile; + pg_time_usec_t now = pg_time_now() + epoch_shift; + + Assert(use_log); + + /* + * Skip the log entry if sampling is enabled and this row doesn't belong + * to the random sample. + */ + if (sample_rate != 0.0 && + pg_prng_double(&thread->ts_sample_rs) > sample_rate) + return; + + /* should we aggregate the results or not? */ + if (agg_interval > 0) + { + pg_time_usec_t next; + + /* + * Loop until we reach the interval of the current moment, and print + * any empty intervals in between (this may happen with very low tps, + * e.g. --rate=0.1). + */ + + while ((next = agg->start_time + agg_interval * INT64CONST(1000000)) <= now) + { + double lag_sum = 0.0; + double lag_sum2 = 0.0; + double lag_min = 0.0; + double lag_max = 0.0; + int64 skipped = 0; + int64 serialization_failures = 0; + int64 deadlock_failures = 0; + int64 retried = 0; + int64 retries = 0; + + /* print aggregated report to logfile */ + fprintf(logfile, INT64_FORMAT " " INT64_FORMAT " %.0f %.0f %.0f %.0f", + agg->start_time / 1000000, /* seconds since Unix epoch */ + agg->cnt, + agg->latency.sum, + agg->latency.sum2, + agg->latency.min, + agg->latency.max); + + if (throttle_delay) + { + lag_sum = agg->lag.sum; + lag_sum2 = agg->lag.sum2; + lag_min = agg->lag.min; + lag_max = agg->lag.max; + } + fprintf(logfile, " %.0f %.0f %.0f %.0f", + lag_sum, + lag_sum2, + lag_min, + lag_max); + + if (latency_limit) + skipped = agg->skipped; + fprintf(logfile, " " INT64_FORMAT, skipped); + + if (max_tries != 1) + { + retried = agg->retried; + retries = agg->retries; + } + fprintf(logfile, " " INT64_FORMAT " " INT64_FORMAT, retried, retries); + + if (failures_detailed) + { + serialization_failures = agg->serialization_failures; + deadlock_failures = agg->deadlock_failures; + } + fprintf(logfile, " " INT64_FORMAT " " INT64_FORMAT, + serialization_failures, + deadlock_failures); + + fputc('\n', logfile); + + /* reset data and move to next interval */ + initStats(agg, next); + } + + /* accumulate the current transaction */ + accumStats(agg, skipped, latency, lag, st->estatus, st->tries); + } + else + { + /* no, print raw transactions */ + if (!skipped && st->estatus == ESTATUS_NO_ERROR) + fprintf(logfile, "%d " INT64_FORMAT " %.0f %d " INT64_FORMAT " " + INT64_FORMAT, + st->id, st->cnt, latency, st->use_file, + now / 1000000, now % 1000000); + else + fprintf(logfile, "%d " INT64_FORMAT " %s %d " INT64_FORMAT " " + INT64_FORMAT, + st->id, st->cnt, getResultString(skipped, st->estatus), + st->use_file, now / 1000000, now % 1000000); + + if (throttle_delay) + fprintf(logfile, " %.0f", lag); + if (max_tries != 1) + fprintf(logfile, " %u", st->tries - 1); + fputc('\n', logfile); + } +} + +/* + * Accumulate and report statistics at end of a transaction. + * + * (This is also called when a transaction is late and thus skipped. + * Note that even skipped and failed transactions are counted in the CState + * "cnt" field.) + */ +static void +processXactStats(TState *thread, CState *st, pg_time_usec_t *now, + bool skipped, StatsData *agg) +{ + double latency = 0.0, + lag = 0.0; + bool detailed = progress || throttle_delay || latency_limit || + use_log || per_script_stats; + + if (detailed && !skipped && st->estatus == ESTATUS_NO_ERROR) + { + pg_time_now_lazy(now); + + /* compute latency & lag */ + latency = (*now) - st->txn_scheduled; + lag = st->txn_begin - st->txn_scheduled; + } + + /* keep detailed thread stats */ + accumStats(&thread->stats, skipped, latency, lag, st->estatus, st->tries); + + /* count transactions over the latency limit, if needed */ + if (latency_limit && latency > latency_limit) + thread->latency_late++; + + /* client stat is just counting */ + st->cnt++; + + if (use_log) + doLog(thread, st, agg, skipped, latency, lag); + + /* XXX could use a mutex here, but we choose not to */ + if (per_script_stats) + accumStats(&sql_script[st->use_file].stats, skipped, latency, lag, + st->estatus, st->tries); +} + + +/* discard connections */ +static void +disconnect_all(CState *state, int length) +{ + int i; + + for (i = 0; i < length; i++) + finishCon(&state[i]); +} + +/* + * Remove old pgbench tables, if any exist + */ +static void +initDropTables(PGconn *con) +{ + fprintf(stderr, "dropping old tables...\n"); + + /* + * We drop all the tables in one command, so that whether there are + * foreign key dependencies or not doesn't matter. + */ + executeStatement(con, "drop table if exists " + "pgbench_accounts, " + "pgbench_branches, " + "pgbench_history, " + "pgbench_tellers"); +} + +/* + * Create "pgbench_accounts" partitions if needed. + * + * This is the larger table of pgbench default tpc-b like schema + * with a known size, so we choose to partition it. + */ +static void +createPartitions(PGconn *con) +{ + PQExpBufferData query; + + /* we must have to create some partitions */ + Assert(partitions > 0); + + fprintf(stderr, "creating %d partitions...\n", partitions); + + initPQExpBuffer(&query); + + for (int p = 1; p <= partitions; p++) + { + if (partition_method == PART_RANGE) + { + int64 part_size = (naccounts * (int64) scale + partitions - 1) / partitions; + + printfPQExpBuffer(&query, + "create%s table pgbench_accounts_%d\n" + " partition of pgbench_accounts\n" + " for values from (", + unlogged_tables ? " unlogged" : "", p); + + /* + * For RANGE, we use open-ended partitions at the beginning and + * end to allow any valid value for the primary key. Although the + * actual minimum and maximum values can be derived from the + * scale, it is more generic and the performance is better. + */ + if (p == 1) + appendPQExpBufferStr(&query, "minvalue"); + else + appendPQExpBuffer(&query, INT64_FORMAT, (p - 1) * part_size + 1); + + appendPQExpBufferStr(&query, ") to ("); + + if (p < partitions) + appendPQExpBuffer(&query, INT64_FORMAT, p * part_size + 1); + else + appendPQExpBufferStr(&query, "maxvalue"); + + appendPQExpBufferChar(&query, ')'); + } + else if (partition_method == PART_HASH) + printfPQExpBuffer(&query, + "create%s table pgbench_accounts_%d\n" + " partition of pgbench_accounts\n" + " for values with (modulus %d, remainder %d)", + unlogged_tables ? " unlogged" : "", p, + partitions, p - 1); + else /* cannot get there */ + Assert(0); + + /* + * Per ddlinfo in initCreateTables, fillfactor is needed on table + * pgbench_accounts. + */ + appendPQExpBuffer(&query, " with (fillfactor=%d)", fillfactor); + + executeStatement(con, query.data); + } + + termPQExpBuffer(&query); +} + +/* + * Create pgbench's standard tables + */ +static void +initCreateTables(PGconn *con) +{ + /* + * Note: TPC-B requires at least 100 bytes per row, and the "filler" + * fields in these table declarations were intended to comply with that. + * The pgbench_accounts table complies with that because the "filler" + * column is set to blank-padded empty string. But for all other tables + * the columns default to NULL and so don't actually take any space. We + * could fix that by giving them non-null default values. However, that + * would completely break comparability of pgbench results with prior + * versions. Since pgbench has never pretended to be fully TPC-B compliant + * anyway, we stick with the historical behavior. + */ + struct ddlinfo + { + const char *table; /* table name */ + const char *smcols; /* column decls if accountIDs are 32 bits */ + const char *bigcols; /* column decls if accountIDs are 64 bits */ + int declare_fillfactor; + }; + static const struct ddlinfo DDLs[] = { + { + "pgbench_history", + "tid int,bid int,aid int,delta int,mtime timestamp,filler char(22)", + "tid int,bid int,aid bigint,delta int,mtime timestamp,filler char(22)", + 0 + }, + { + "pgbench_tellers", + "tid int not null,bid int,tbalance int,filler char(84)", + "tid int not null,bid int,tbalance int,filler char(84)", + 1 + }, + { + "pgbench_accounts", + "aid int not null,bid int,abalance int,filler char(84)", + "aid bigint not null,bid int,abalance int,filler char(84)", + 1 + }, + { + "pgbench_branches", + "bid int not null,bbalance int,filler char(88)", + "bid int not null,bbalance int,filler char(88)", + 1 + } + }; + int i; + PQExpBufferData query; + + fprintf(stderr, "creating tables...\n"); + + initPQExpBuffer(&query); + + for (i = 0; i < lengthof(DDLs); i++) + { + const struct ddlinfo *ddl = &DDLs[i]; + + /* Construct new create table statement. */ + printfPQExpBuffer(&query, "create%s table %s(%s)", + unlogged_tables ? " unlogged" : "", + ddl->table, + (scale >= SCALE_32BIT_THRESHOLD) ? ddl->bigcols : ddl->smcols); + + /* Partition pgbench_accounts table */ + if (partition_method != PART_NONE && strcmp(ddl->table, "pgbench_accounts") == 0) + appendPQExpBuffer(&query, + " partition by %s (aid)", PARTITION_METHOD[partition_method]); + else if (ddl->declare_fillfactor) + { + /* fillfactor is only expected on actual tables */ + appendPQExpBuffer(&query, " with (fillfactor=%d)", fillfactor); + } + + if (tablespace != NULL) + { + char *escape_tablespace; + + escape_tablespace = PQescapeIdentifier(con, tablespace, strlen(tablespace)); + appendPQExpBuffer(&query, " tablespace %s", escape_tablespace); + PQfreemem(escape_tablespace); + } + + executeStatement(con, query.data); + } + + termPQExpBuffer(&query); + + if (partition_method != PART_NONE) + createPartitions(con); +} + +/* + * Truncate away any old data, in one command in case there are foreign keys + */ +static void +initTruncateTables(PGconn *con) +{ + executeStatement(con, "truncate table " + "pgbench_accounts, " + "pgbench_branches, " + "pgbench_history, " + "pgbench_tellers"); +} + +/* + * Fill the standard tables with some data generated and sent from the client + */ +static void +initGenerateDataClientSide(PGconn *con) +{ + PQExpBufferData sql; + PGresult *res; + int i; + int64 k; + char *copy_statement; + + /* used to track elapsed time and estimate of the remaining time */ + pg_time_usec_t start; + int log_interval = 1; + + /* Stay on the same line if reporting to a terminal */ + char eol = isatty(fileno(stderr)) ? '\r' : '\n'; + + fprintf(stderr, "generating data (client-side)...\n"); + + /* + * we do all of this in one transaction to enable the backend's + * data-loading optimizations + */ + executeStatement(con, "begin"); + + /* truncate away any old data */ + initTruncateTables(con); + + initPQExpBuffer(&sql); + + /* + * fill branches, tellers, accounts in that order in case foreign keys + * already exist + */ + for (i = 0; i < nbranches * scale; i++) + { + /* "filler" column defaults to NULL */ + printfPQExpBuffer(&sql, + "insert into pgbench_branches(bid,bbalance) values(%d,0)", + i + 1); + executeStatement(con, sql.data); + } + + for (i = 0; i < ntellers * scale; i++) + { + /* "filler" column defaults to NULL */ + printfPQExpBuffer(&sql, + "insert into pgbench_tellers(tid,bid,tbalance) values (%d,%d,0)", + i + 1, i / ntellers + 1); + executeStatement(con, sql.data); + } + + /* + * accounts is big enough to be worth using COPY and tracking runtime + */ + + /* use COPY with FREEZE on v14 and later without partitioning */ + if (partitions == 0 && PQserverVersion(con) >= 140000) + copy_statement = "copy pgbench_accounts from stdin with (freeze on)"; + else + copy_statement = "copy pgbench_accounts from stdin"; + + res = PQexec(con, copy_statement); + + if (PQresultStatus(res) != PGRES_COPY_IN) + pg_fatal("unexpected copy in result: %s", PQerrorMessage(con)); + PQclear(res); + + start = pg_time_now(); + + for (k = 0; k < (int64) naccounts * scale; k++) + { + int64 j = k + 1; + + /* "filler" column defaults to blank padded empty string */ + printfPQExpBuffer(&sql, + INT64_FORMAT "\t" INT64_FORMAT "\t%d\t\n", + j, k / naccounts + 1, 0); + if (PQputline(con, sql.data)) + pg_fatal("PQputline failed"); + + if (CancelRequested) + break; + + /* + * If we want to stick with the original logging, print a message each + * 100k inserted rows. + */ + if ((!use_quiet) && (j % 100000 == 0)) + { + double elapsed_sec = PG_TIME_GET_DOUBLE(pg_time_now() - start); + double remaining_sec = ((double) scale * naccounts - j) * elapsed_sec / j; + + fprintf(stderr, INT64_FORMAT " of " INT64_FORMAT " tuples (%d%%) done (elapsed %.2f s, remaining %.2f s)%c", + j, (int64) naccounts * scale, + (int) (((int64) j * 100) / (naccounts * (int64) scale)), + elapsed_sec, remaining_sec, eol); + } + /* let's not call the timing for each row, but only each 100 rows */ + else if (use_quiet && (j % 100 == 0)) + { + double elapsed_sec = PG_TIME_GET_DOUBLE(pg_time_now() - start); + double remaining_sec = ((double) scale * naccounts - j) * elapsed_sec / j; + + /* have we reached the next interval (or end)? */ + if ((j == scale * naccounts) || (elapsed_sec >= log_interval * LOG_STEP_SECONDS)) + { + fprintf(stderr, INT64_FORMAT " of " INT64_FORMAT " tuples (%d%%) done (elapsed %.2f s, remaining %.2f s)%c", + j, (int64) naccounts * scale, + (int) (((int64) j * 100) / (naccounts * (int64) scale)), elapsed_sec, remaining_sec, eol); + + /* skip to the next interval */ + log_interval = (int) ceil(elapsed_sec / LOG_STEP_SECONDS); + } + } + } + + if (eol != '\n') + fputc('\n', stderr); /* Need to move to next line */ + + if (PQputline(con, "\\.\n")) + pg_fatal("very last PQputline failed"); + if (PQendcopy(con)) + pg_fatal("PQendcopy failed"); + + termPQExpBuffer(&sql); + + executeStatement(con, "commit"); +} + +/* + * Fill the standard tables with some data generated on the server + * + * As already the case with the client-side data generation, the filler + * column defaults to NULL in pgbench_branches and pgbench_tellers, + * and is a blank-padded string in pgbench_accounts. + */ +static void +initGenerateDataServerSide(PGconn *con) +{ + PQExpBufferData sql; + + fprintf(stderr, "generating data (server-side)...\n"); + + /* + * we do all of this in one transaction to enable the backend's + * data-loading optimizations + */ + executeStatement(con, "begin"); + + /* truncate away any old data */ + initTruncateTables(con); + + initPQExpBuffer(&sql); + + printfPQExpBuffer(&sql, + "insert into pgbench_branches(bid,bbalance) " + "select bid, 0 " + "from generate_series(1, %d) as bid", nbranches * scale); + executeStatement(con, sql.data); + + printfPQExpBuffer(&sql, + "insert into pgbench_tellers(tid,bid,tbalance) " + "select tid, (tid - 1) / %d + 1, 0 " + "from generate_series(1, %d) as tid", ntellers, ntellers * scale); + executeStatement(con, sql.data); + + printfPQExpBuffer(&sql, + "insert into pgbench_accounts(aid,bid,abalance,filler) " + "select aid, (aid - 1) / %d + 1, 0, '' " + "from generate_series(1, " INT64_FORMAT ") as aid", + naccounts, (int64) naccounts * scale); + executeStatement(con, sql.data); + + termPQExpBuffer(&sql); + + executeStatement(con, "commit"); +} + +/* + * Invoke vacuum on the standard tables + */ +static void +initVacuum(PGconn *con) +{ + fprintf(stderr, "vacuuming...\n"); + executeStatement(con, "vacuum analyze pgbench_branches"); + executeStatement(con, "vacuum analyze pgbench_tellers"); + executeStatement(con, "vacuum analyze pgbench_accounts"); + executeStatement(con, "vacuum analyze pgbench_history"); +} + +/* + * Create primary keys on the standard tables + */ +static void +initCreatePKeys(PGconn *con) +{ + static const char *const DDLINDEXes[] = { + "alter table pgbench_branches add primary key (bid)", + "alter table pgbench_tellers add primary key (tid)", + "alter table pgbench_accounts add primary key (aid)" + }; + int i; + PQExpBufferData query; + + fprintf(stderr, "creating primary keys...\n"); + initPQExpBuffer(&query); + + for (i = 0; i < lengthof(DDLINDEXes); i++) + { + resetPQExpBuffer(&query); + appendPQExpBufferStr(&query, DDLINDEXes[i]); + + if (index_tablespace != NULL) + { + char *escape_tablespace; + + escape_tablespace = PQescapeIdentifier(con, index_tablespace, + strlen(index_tablespace)); + appendPQExpBuffer(&query, " using index tablespace %s", escape_tablespace); + PQfreemem(escape_tablespace); + } + + executeStatement(con, query.data); + } + + termPQExpBuffer(&query); +} + +/* + * Create foreign key constraints between the standard tables + */ +static void +initCreateFKeys(PGconn *con) +{ + static const char *const DDLKEYs[] = { + "alter table pgbench_tellers add constraint pgbench_tellers_bid_fkey foreign key (bid) references pgbench_branches", + "alter table pgbench_accounts add constraint pgbench_accounts_bid_fkey foreign key (bid) references pgbench_branches", + "alter table pgbench_history add constraint pgbench_history_bid_fkey foreign key (bid) references pgbench_branches", + "alter table pgbench_history add constraint pgbench_history_tid_fkey foreign key (tid) references pgbench_tellers", + "alter table pgbench_history add constraint pgbench_history_aid_fkey foreign key (aid) references pgbench_accounts" + }; + int i; + + fprintf(stderr, "creating foreign keys...\n"); + for (i = 0; i < lengthof(DDLKEYs); i++) + { + executeStatement(con, DDLKEYs[i]); + } +} + +/* + * Validate an initialization-steps string + * + * (We could just leave it to runInitSteps() to fail if there are wrong + * characters, but since initialization can take awhile, it seems friendlier + * to check during option parsing.) + */ +static void +checkInitSteps(const char *initialize_steps) +{ + if (initialize_steps[0] == '\0') + pg_fatal("no initialization steps specified"); + + for (const char *step = initialize_steps; *step != '\0'; step++) + { + if (strchr(ALL_INIT_STEPS " ", *step) == NULL) + { + pg_log_error("unrecognized initialization step \"%c\"", *step); + pg_log_error_detail("Allowed step characters are: \"" ALL_INIT_STEPS "\"."); + exit(1); + } + } +} + +/* + * Invoke each initialization step in the given string + */ +static void +runInitSteps(const char *initialize_steps) +{ + PQExpBufferData stats; + PGconn *con; + const char *step; + double run_time = 0.0; + bool first = true; + + initPQExpBuffer(&stats); + + if ((con = doConnect()) == NULL) + pg_fatal("could not create connection for initialization"); + + setup_cancel_handler(NULL); + SetCancelConn(con); + + for (step = initialize_steps; *step != '\0'; step++) + { + char *op = NULL; + pg_time_usec_t start = pg_time_now(); + + switch (*step) + { + case 'd': + op = "drop tables"; + initDropTables(con); + break; + case 't': + op = "create tables"; + initCreateTables(con); + break; + case 'g': + op = "client-side generate"; + initGenerateDataClientSide(con); + break; + case 'G': + op = "server-side generate"; + initGenerateDataServerSide(con); + break; + case 'v': + op = "vacuum"; + initVacuum(con); + break; + case 'p': + op = "primary keys"; + initCreatePKeys(con); + break; + case 'f': + op = "foreign keys"; + initCreateFKeys(con); + break; + case ' ': + break; /* ignore */ + default: + pg_log_error("unrecognized initialization step \"%c\"", *step); + PQfinish(con); + exit(1); + } + + if (op != NULL) + { + double elapsed_sec = PG_TIME_GET_DOUBLE(pg_time_now() - start); + + if (!first) + appendPQExpBufferStr(&stats, ", "); + else + first = false; + + appendPQExpBuffer(&stats, "%s %.2f s", op, elapsed_sec); + + run_time += elapsed_sec; + } + } + + fprintf(stderr, "done in %.2f s (%s).\n", run_time, stats.data); + ResetCancelConn(); + PQfinish(con); + termPQExpBuffer(&stats); +} + +/* + * Extract pgbench table information into global variables scale, + * partition_method and partitions. + */ +static void +GetTableInfo(PGconn *con, bool scale_given) +{ + PGresult *res; + + /* + * get the scaling factor that should be same as count(*) from + * pgbench_branches if this is not a custom query + */ + res = PQexec(con, "select count(*) from pgbench_branches"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + char *sqlState = PQresultErrorField(res, PG_DIAG_SQLSTATE); + + pg_log_error("could not count number of branches: %s", PQerrorMessage(con)); + + if (sqlState && strcmp(sqlState, ERRCODE_UNDEFINED_TABLE) == 0) + pg_log_error_hint("Perhaps you need to do initialization (\"pgbench -i\") in database \"%s\".", + PQdb(con)); + + exit(1); + } + scale = atoi(PQgetvalue(res, 0, 0)); + if (scale < 0) + pg_fatal("invalid count(*) from pgbench_branches: \"%s\"", + PQgetvalue(res, 0, 0)); + PQclear(res); + + /* warn if we override user-given -s switch */ + if (scale_given) + pg_log_warning("scale option ignored, using count from pgbench_branches table (%d)", + scale); + + /* + * Get the partition information for the first "pgbench_accounts" table + * found in search_path. + * + * The result is empty if no "pgbench_accounts" is found. + * + * Otherwise, it always returns one row even if the table is not + * partitioned (in which case the partition strategy is NULL). + * + * The number of partitions can be 0 even for partitioned tables, if no + * partition is attached. + * + * We assume no partitioning on any failure, so as to avoid failing on an + * old version without "pg_partitioned_table". + */ + res = PQexec(con, + "select o.n, p.partstrat, pg_catalog.count(i.inhparent) " + "from pg_catalog.pg_class as c " + "join pg_catalog.pg_namespace as n on (n.oid = c.relnamespace) " + "cross join lateral (select pg_catalog.array_position(pg_catalog.current_schemas(true), n.nspname)) as o(n) " + "left join pg_catalog.pg_partitioned_table as p on (p.partrelid = c.oid) " + "left join pg_catalog.pg_inherits as i on (c.oid = i.inhparent) " + "where c.relname = 'pgbench_accounts' and o.n is not null " + "group by 1, 2 " + "order by 1 asc " + "limit 1"); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + /* probably an older version, coldly assume no partitioning */ + partition_method = PART_NONE; + partitions = 0; + } + else if (PQntuples(res) == 0) + { + /* + * This case is unlikely as pgbench already found "pgbench_branches" + * above to compute the scale. + */ + pg_log_error("no pgbench_accounts table found in search_path"); + pg_log_error_hint("Perhaps you need to do initialization (\"pgbench -i\") in database \"%s\".", PQdb(con)); + exit(1); + } + else /* PQntupes(res) == 1 */ + { + /* normal case, extract partition information */ + if (PQgetisnull(res, 0, 1)) + partition_method = PART_NONE; + else + { + char *ps = PQgetvalue(res, 0, 1); + + /* column must be there */ + Assert(ps != NULL); + + if (strcmp(ps, "r") == 0) + partition_method = PART_RANGE; + else if (strcmp(ps, "h") == 0) + partition_method = PART_HASH; + else + { + /* possibly a newer version with new partition method */ + pg_fatal("unexpected partition method: \"%s\"", ps); + } + } + + partitions = atoi(PQgetvalue(res, 0, 2)); + } + + PQclear(res); +} + +/* + * Replace :param with $n throughout the command's SQL text, which + * is a modifiable string in cmd->lines. + */ +static bool +parseQuery(Command *cmd) +{ + char *sql, + *p; + + cmd->argc = 1; + + p = sql = pg_strdup(cmd->lines.data); + while ((p = strchr(p, ':')) != NULL) + { + char var[13]; + char *name; + int eaten; + + name = parseVariable(p, &eaten); + if (name == NULL) + { + while (*p == ':') + { + p++; + } + continue; + } + + /* + * cmd->argv[0] is the SQL statement itself, so the max number of + * arguments is one less than MAX_ARGS + */ + if (cmd->argc >= MAX_ARGS) + { + pg_log_error("statement has too many arguments (maximum is %d): %s", + MAX_ARGS - 1, cmd->lines.data); + pg_free(name); + return false; + } + + sprintf(var, "$%d", cmd->argc); + p = replaceVariable(&sql, p, eaten, var); + + cmd->argv[cmd->argc] = name; + cmd->argc++; + } + + Assert(cmd->argv[0] == NULL); + cmd->argv[0] = sql; + return true; +} + +/* + * syntax error while parsing a script (in practice, while parsing a + * backslash command, because we don't detect syntax errors in SQL) + * + * source: source of script (filename or builtin-script ID) + * lineno: line number within script (count from 1) + * line: whole line of backslash command, if available + * command: backslash command name, if available + * msg: the actual error message + * more: optional extra message + * column: zero-based column number, or -1 if unknown + */ +void +syntax_error(const char *source, int lineno, + const char *line, const char *command, + const char *msg, const char *more, int column) +{ + PQExpBufferData buf; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, "%s:%d: %s", source, lineno, msg); + if (more != NULL) + appendPQExpBuffer(&buf, " (%s)", more); + if (column >= 0 && line == NULL) + appendPQExpBuffer(&buf, " at column %d", column + 1); + if (command != NULL) + appendPQExpBuffer(&buf, " in command \"%s\"", command); + + pg_log_error("%s", buf.data); + + termPQExpBuffer(&buf); + + if (line != NULL) + { + fprintf(stderr, "%s\n", line); + if (column >= 0) + fprintf(stderr, "%*c error found here\n", column + 1, '^'); + } + + exit(1); +} + +/* + * Return a pointer to the start of the SQL command, after skipping over + * whitespace and "--" comments. + * If the end of the string is reached, return NULL. + */ +static char * +skip_sql_comments(char *sql_command) +{ + char *p = sql_command; + + /* Skip any leading whitespace, as well as "--" style comments */ + for (;;) + { + if (isspace((unsigned char) *p)) + p++; + else if (strncmp(p, "--", 2) == 0) + { + p = strchr(p, '\n'); + if (p == NULL) + return NULL; + p++; + } + else + break; + } + + /* NULL if there's nothing but whitespace and comments */ + if (*p == '\0') + return NULL; + + return p; +} + +/* + * Parse a SQL command; return a Command struct, or NULL if it's a comment + * + * On entry, psqlscan.l has collected the command into "buf", so we don't + * really need to do much here except check for comments and set up a Command + * struct. + */ +static Command * +create_sql_command(PQExpBuffer buf, const char *source) +{ + Command *my_command; + char *p = skip_sql_comments(buf->data); + + if (p == NULL) + return NULL; + + /* Allocate and initialize Command structure */ + my_command = (Command *) pg_malloc(sizeof(Command)); + initPQExpBuffer(&my_command->lines); + appendPQExpBufferStr(&my_command->lines, p); + my_command->first_line = NULL; /* this is set later */ + my_command->type = SQL_COMMAND; + my_command->meta = META_NONE; + my_command->argc = 0; + my_command->retries = 0; + my_command->failures = 0; + memset(my_command->argv, 0, sizeof(my_command->argv)); + my_command->varprefix = NULL; /* allocated later, if needed */ + my_command->expr = NULL; + initSimpleStats(&my_command->stats); + my_command->prepname = NULL; /* set later, if needed */ + + return my_command; +} + +/* Free a Command structure and associated data */ +static void +free_command(Command *command) +{ + termPQExpBuffer(&command->lines); + if (command->first_line) + pg_free(command->first_line); + for (int i = 0; i < command->argc; i++) + pg_free(command->argv[i]); + if (command->varprefix) + pg_free(command->varprefix); + + /* + * It should also free expr recursively, but this is currently not needed + * as only gset commands (which do not have an expression) are freed. + */ + pg_free(command); +} + +/* + * Once an SQL command is fully parsed, possibly by accumulating several + * parts, complete other fields of the Command structure. + */ +static void +postprocess_sql_command(Command *my_command) +{ + char buffer[128]; + static int prepnum = 0; + + Assert(my_command->type == SQL_COMMAND); + + /* Save the first line for error display. */ + strlcpy(buffer, my_command->lines.data, sizeof(buffer)); + buffer[strcspn(buffer, "\n\r")] = '\0'; + my_command->first_line = pg_strdup(buffer); + + /* Parse query and generate prepared statement name, if necessary */ + switch (querymode) + { + case QUERY_SIMPLE: + my_command->argv[0] = my_command->lines.data; + my_command->argc++; + break; + case QUERY_PREPARED: + my_command->prepname = psprintf("P_%d", prepnum++); + /* fall through */ + case QUERY_EXTENDED: + if (!parseQuery(my_command)) + exit(1); + break; + default: + exit(1); + } +} + +/* + * Parse a backslash command; return a Command struct, or NULL if comment + * + * At call, we have scanned only the initial backslash. + */ +static Command * +process_backslash_command(PsqlScanState sstate, const char *source) +{ + Command *my_command; + PQExpBufferData word_buf; + int word_offset; + int offsets[MAX_ARGS]; /* offsets of argument words */ + int start_offset; + int lineno; + int j; + + initPQExpBuffer(&word_buf); + + /* Remember location of the backslash */ + start_offset = expr_scanner_offset(sstate) - 1; + lineno = expr_scanner_get_lineno(sstate, start_offset); + + /* Collect first word of command */ + if (!expr_lex_one_word(sstate, &word_buf, &word_offset)) + { + termPQExpBuffer(&word_buf); + return NULL; + } + + /* Allocate and initialize Command structure */ + my_command = (Command *) pg_malloc0(sizeof(Command)); + my_command->type = META_COMMAND; + my_command->argc = 0; + initSimpleStats(&my_command->stats); + + /* Save first word (command name) */ + j = 0; + offsets[j] = word_offset; + my_command->argv[j++] = pg_strdup(word_buf.data); + my_command->argc++; + + /* ... and convert it to enum form */ + my_command->meta = getMetaCommand(my_command->argv[0]); + + if (my_command->meta == META_SET || + my_command->meta == META_IF || + my_command->meta == META_ELIF) + { + yyscan_t yyscanner; + + /* For \set, collect var name */ + if (my_command->meta == META_SET) + { + if (!expr_lex_one_word(sstate, &word_buf, &word_offset)) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "missing argument", NULL, -1); + + offsets[j] = word_offset; + my_command->argv[j++] = pg_strdup(word_buf.data); + my_command->argc++; + } + + /* then for all parse the expression */ + yyscanner = expr_scanner_init(sstate, source, lineno, start_offset, + my_command->argv[0]); + + if (expr_yyparse(yyscanner) != 0) + { + /* dead code: exit done from syntax_error called by yyerror */ + exit(1); + } + + my_command->expr = expr_parse_result; + + /* Save line, trimming any trailing newline */ + my_command->first_line = + expr_scanner_get_substring(sstate, + start_offset, + expr_scanner_offset(sstate), + true); + + expr_scanner_finish(yyscanner); + + termPQExpBuffer(&word_buf); + + return my_command; + } + + /* For all other commands, collect remaining words. */ + while (expr_lex_one_word(sstate, &word_buf, &word_offset)) + { + /* + * my_command->argv[0] is the command itself, so the max number of + * arguments is one less than MAX_ARGS + */ + if (j >= MAX_ARGS) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "too many arguments", NULL, -1); + + offsets[j] = word_offset; + my_command->argv[j++] = pg_strdup(word_buf.data); + my_command->argc++; + } + + /* Save line, trimming any trailing newline */ + my_command->first_line = + expr_scanner_get_substring(sstate, + start_offset, + expr_scanner_offset(sstate), + true); + + if (my_command->meta == META_SLEEP) + { + if (my_command->argc < 2) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "missing argument", NULL, -1); + + if (my_command->argc > 3) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "too many arguments", NULL, + offsets[3] - start_offset); + + /* + * Split argument into number and unit to allow "sleep 1ms" etc. We + * don't have to terminate the number argument with null because it + * will be parsed with atoi, which ignores trailing non-digit + * characters. + */ + if (my_command->argv[1][0] != ':') + { + char *c = my_command->argv[1]; + bool have_digit = false; + + /* Skip sign */ + if (*c == '+' || *c == '-') + c++; + + /* Require at least one digit */ + if (*c && isdigit((unsigned char) *c)) + have_digit = true; + + /* Eat all digits */ + while (*c && isdigit((unsigned char) *c)) + c++; + + if (*c) + { + if (my_command->argc == 2 && have_digit) + { + my_command->argv[2] = c; + offsets[2] = offsets[1] + (c - my_command->argv[1]); + my_command->argc = 3; + } + else + { + /* + * Raise an error if argument starts with non-digit + * character (after sign). + */ + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "invalid sleep time, must be an integer", + my_command->argv[1], offsets[1] - start_offset); + } + } + } + + if (my_command->argc == 3) + { + if (pg_strcasecmp(my_command->argv[2], "us") != 0 && + pg_strcasecmp(my_command->argv[2], "ms") != 0 && + pg_strcasecmp(my_command->argv[2], "s") != 0) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "unrecognized time unit, must be us, ms or s", + my_command->argv[2], offsets[2] - start_offset); + } + } + else if (my_command->meta == META_SETSHELL) + { + if (my_command->argc < 3) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "missing argument", NULL, -1); + } + else if (my_command->meta == META_SHELL) + { + if (my_command->argc < 2) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "missing command", NULL, -1); + } + else if (my_command->meta == META_ELSE || my_command->meta == META_ENDIF || + my_command->meta == META_STARTPIPELINE || + my_command->meta == META_ENDPIPELINE) + { + if (my_command->argc != 1) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "unexpected argument", NULL, -1); + } + else if (my_command->meta == META_GSET || my_command->meta == META_ASET) + { + if (my_command->argc > 2) + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "too many arguments", NULL, -1); + } + else + { + /* my_command->meta == META_NONE */ + syntax_error(source, lineno, my_command->first_line, my_command->argv[0], + "invalid command", NULL, -1); + } + + termPQExpBuffer(&word_buf); + + return my_command; +} + +static void +ConditionError(const char *desc, int cmdn, const char *msg) +{ + pg_fatal("condition error in script \"%s\" command %d: %s", + desc, cmdn, msg); +} + +/* + * Partial evaluation of conditionals before recording and running the script. + */ +static void +CheckConditional(const ParsedScript *ps) +{ + /* statically check conditional structure */ + ConditionalStack cs = conditional_stack_create(); + int i; + + for (i = 0; ps->commands[i] != NULL; i++) + { + Command *cmd = ps->commands[i]; + + if (cmd->type == META_COMMAND) + { + switch (cmd->meta) + { + case META_IF: + conditional_stack_push(cs, IFSTATE_FALSE); + break; + case META_ELIF: + if (conditional_stack_empty(cs)) + ConditionError(ps->desc, i + 1, "\\elif without matching \\if"); + if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE) + ConditionError(ps->desc, i + 1, "\\elif after \\else"); + break; + case META_ELSE: + if (conditional_stack_empty(cs)) + ConditionError(ps->desc, i + 1, "\\else without matching \\if"); + if (conditional_stack_peek(cs) == IFSTATE_ELSE_FALSE) + ConditionError(ps->desc, i + 1, "\\else after \\else"); + conditional_stack_poke(cs, IFSTATE_ELSE_FALSE); + break; + case META_ENDIF: + if (!conditional_stack_pop(cs)) + ConditionError(ps->desc, i + 1, "\\endif without matching \\if"); + break; + default: + /* ignore anything else... */ + break; + } + } + } + if (!conditional_stack_empty(cs)) + ConditionError(ps->desc, i + 1, "\\if without matching \\endif"); + conditional_stack_destroy(cs); +} + +/* + * Parse a script (either the contents of a file, or a built-in script) + * and add it to the list of scripts. + */ +static void +ParseScript(const char *script, const char *desc, int weight) +{ + ParsedScript ps; + PsqlScanState sstate; + PQExpBufferData line_buf; + int alloc_num; + int index; + int lineno; + int start_offset; + +#define COMMANDS_ALLOC_NUM 128 + alloc_num = COMMANDS_ALLOC_NUM; + + /* Initialize all fields of ps */ + ps.desc = desc; + ps.weight = weight; + ps.commands = (Command **) pg_malloc(sizeof(Command *) * alloc_num); + initStats(&ps.stats, 0); + + /* Prepare to parse script */ + sstate = psql_scan_create(&pgbench_callbacks); + + /* + * Ideally, we'd scan scripts using the encoding and stdstrings settings + * we get from a DB connection. However, without major rearrangement of + * pgbench's argument parsing, we can't have a DB connection at the time + * we parse scripts. Using SQL_ASCII (encoding 0) should work well enough + * with any backend-safe encoding, though conceivably we could be fooled + * if a script file uses a client-only encoding. We also assume that + * stdstrings should be true, which is a bit riskier. + */ + psql_scan_setup(sstate, script, strlen(script), 0, true); + start_offset = expr_scanner_offset(sstate) - 1; + + initPQExpBuffer(&line_buf); + + index = 0; + + for (;;) + { + PsqlScanResult sr; + promptStatus_t prompt; + Command *command = NULL; + + resetPQExpBuffer(&line_buf); + lineno = expr_scanner_get_lineno(sstate, start_offset); + + sr = psql_scan(sstate, &line_buf, &prompt); + + /* If we collected a new SQL command, process that */ + command = create_sql_command(&line_buf, desc); + + /* store new command */ + if (command) + ps.commands[index++] = command; + + /* If we reached a backslash, process that */ + if (sr == PSCAN_BACKSLASH) + { + command = process_backslash_command(sstate, desc); + + if (command) + { + /* + * If this is gset or aset, merge into the preceding command. + * (We don't use a command slot in this case). + */ + if (command->meta == META_GSET || command->meta == META_ASET) + { + Command *cmd; + + if (index == 0) + syntax_error(desc, lineno, NULL, NULL, + "\\gset must follow an SQL command", + NULL, -1); + + cmd = ps.commands[index - 1]; + + if (cmd->type != SQL_COMMAND || + cmd->varprefix != NULL) + syntax_error(desc, lineno, NULL, NULL, + "\\gset must follow an SQL command", + cmd->first_line, -1); + + /* get variable prefix */ + if (command->argc <= 1 || command->argv[1][0] == '\0') + cmd->varprefix = pg_strdup(""); + else + cmd->varprefix = pg_strdup(command->argv[1]); + + /* update the sql command meta */ + cmd->meta = command->meta; + + /* cleanup unused command */ + free_command(command); + + continue; + } + + /* Attach any other backslash command as a new command */ + ps.commands[index++] = command; + } + } + + /* + * Since we used a command slot, allocate more if needed. Note we + * always allocate one more in order to accommodate the NULL + * terminator below. + */ + if (index >= alloc_num) + { + alloc_num += COMMANDS_ALLOC_NUM; + ps.commands = (Command **) + pg_realloc(ps.commands, sizeof(Command *) * alloc_num); + } + + /* Done if we reached EOF */ + if (sr == PSCAN_INCOMPLETE || sr == PSCAN_EOL) + break; + } + + ps.commands[index] = NULL; + + addScript(&ps); + + termPQExpBuffer(&line_buf); + psql_scan_finish(sstate); + psql_scan_destroy(sstate); +} + +/* + * Read the entire contents of file fd, and return it in a malloc'd buffer. + * + * The buffer will typically be larger than necessary, but we don't care + * in this program, because we'll free it as soon as we've parsed the script. + */ +static char * +read_file_contents(FILE *fd) +{ + char *buf; + size_t buflen = BUFSIZ; + size_t used = 0; + + buf = (char *) pg_malloc(buflen); + + for (;;) + { + size_t nread; + + nread = fread(buf + used, 1, BUFSIZ, fd); + used += nread; + /* If fread() read less than requested, must be EOF or error */ + if (nread < BUFSIZ) + break; + /* Enlarge buf so we can read some more */ + buflen += BUFSIZ; + buf = (char *) pg_realloc(buf, buflen); + } + /* There is surely room for a terminator */ + buf[used] = '\0'; + + return buf; +} + +/* + * Given a file name, read it and add its script to the list. + * "-" means to read stdin. + * NB: filename must be storage that won't disappear. + */ +static void +process_file(const char *filename, int weight) +{ + FILE *fd; + char *buf; + + /* Slurp the file contents into "buf" */ + if (strcmp(filename, "-") == 0) + fd = stdin; + else if ((fd = fopen(filename, "r")) == NULL) + pg_fatal("could not open file \"%s\": %m", filename); + + buf = read_file_contents(fd); + + if (ferror(fd)) + pg_fatal("could not read file \"%s\": %m", filename); + + if (fd != stdin) + fclose(fd); + + ParseScript(buf, filename, weight); + + free(buf); +} + +/* Parse the given builtin script and add it to the list. */ +static void +process_builtin(const BuiltinScript *bi, int weight) +{ + ParseScript(bi->script, bi->desc, weight); +} + +/* show available builtin scripts */ +static void +listAvailableScripts(void) +{ + int i; + + fprintf(stderr, "Available builtin scripts:\n"); + for (i = 0; i < lengthof(builtin_script); i++) + fprintf(stderr, " %13s: %s\n", builtin_script[i].name, builtin_script[i].desc); + fprintf(stderr, "\n"); +} + +/* return builtin script "name" if unambiguous, fails if not found */ +static const BuiltinScript * +findBuiltin(const char *name) +{ + int i, + found = 0, + len = strlen(name); + const BuiltinScript *result = NULL; + + for (i = 0; i < lengthof(builtin_script); i++) + { + if (strncmp(builtin_script[i].name, name, len) == 0) + { + result = &builtin_script[i]; + found++; + } + } + + /* ok, unambiguous result */ + if (found == 1) + return result; + + /* error cases */ + if (found == 0) + pg_log_error("no builtin script found for name \"%s\"", name); + else /* found > 1 */ + pg_log_error("ambiguous builtin name: %d builtin scripts found for prefix \"%s\"", found, name); + + listAvailableScripts(); + exit(1); +} + +/* + * Determine the weight specification from a script option (-b, -f), if any, + * and return it as an integer (1 is returned if there's no weight). The + * script name is returned in *script as a malloc'd string. + */ +static int +parseScriptWeight(const char *option, char **script) +{ + char *sep; + int weight; + + if ((sep = strrchr(option, WSEP))) + { + int namelen = sep - option; + long wtmp; + char *badp; + + /* generate the script name */ + *script = pg_malloc(namelen + 1); + strncpy(*script, option, namelen); + (*script)[namelen] = '\0'; + + /* process digits of the weight spec */ + errno = 0; + wtmp = strtol(sep + 1, &badp, 10); + if (errno != 0 || badp == sep + 1 || *badp != '\0') + pg_fatal("invalid weight specification: %s", sep); + if (wtmp > INT_MAX || wtmp < 0) + pg_fatal("weight specification out of range (0 .. %d): %lld", + INT_MAX, (long long) wtmp); + weight = wtmp; + } + else + { + *script = pg_strdup(option); + weight = 1; + } + + return weight; +} + +/* append a script to the list of scripts to process */ +static void +addScript(const ParsedScript *script) +{ + if (script->commands == NULL || script->commands[0] == NULL) + pg_fatal("empty command list for script \"%s\"", script->desc); + + if (num_scripts >= MAX_SCRIPTS) + pg_fatal("at most %d SQL scripts are allowed", MAX_SCRIPTS); + + CheckConditional(script); + + sql_script[num_scripts] = *script; + num_scripts++; +} + +/* + * Print progress report. + * + * On entry, *last and *last_report contain the statistics and time of last + * progress report. On exit, they are updated with the new stats. + */ +static void +printProgressReport(TState *threads, int64 test_start, pg_time_usec_t now, + StatsData *last, int64 *last_report) +{ + /* generate and show report */ + pg_time_usec_t run = now - *last_report; + int64 cnt, + failures, + retried; + double tps, + total_run, + latency, + sqlat, + lag, + stdev; + char tbuf[315]; + StatsData cur; + + /* + * Add up the statistics of all threads. + * + * XXX: No locking. There is no guarantee that we get an atomic snapshot + * of the transaction count and latencies, so these figures can well be + * off by a small amount. The progress report's purpose is to give a + * quick overview of how the test is going, so that shouldn't matter too + * much. (If a read from a 64-bit integer is not atomic, you might get a + * "torn" read and completely bogus latencies though!) + */ + initStats(&cur, 0); + for (int i = 0; i < nthreads; i++) + { + mergeSimpleStats(&cur.latency, &threads[i].stats.latency); + mergeSimpleStats(&cur.lag, &threads[i].stats.lag); + cur.cnt += threads[i].stats.cnt; + cur.skipped += threads[i].stats.skipped; + cur.retries += threads[i].stats.retries; + cur.retried += threads[i].stats.retried; + cur.serialization_failures += + threads[i].stats.serialization_failures; + cur.deadlock_failures += threads[i].stats.deadlock_failures; + } + + /* we count only actually executed transactions */ + cnt = cur.cnt - last->cnt; + total_run = (now - test_start) / 1000000.0; + tps = 1000000.0 * cnt / run; + if (cnt > 0) + { + latency = 0.001 * (cur.latency.sum - last->latency.sum) / cnt; + sqlat = 1.0 * (cur.latency.sum2 - last->latency.sum2) / cnt; + stdev = 0.001 * sqrt(sqlat - 1000000.0 * latency * latency); + lag = 0.001 * (cur.lag.sum - last->lag.sum) / cnt; + } + else + { + latency = sqlat = stdev = lag = 0; + } + failures = getFailures(&cur) - getFailures(last); + retried = cur.retried - last->retried; + + if (progress_timestamp) + { + snprintf(tbuf, sizeof(tbuf), "%.3f s", + PG_TIME_GET_DOUBLE(now + epoch_shift)); + } + else + { + /* round seconds are expected, but the thread may be late */ + snprintf(tbuf, sizeof(tbuf), "%.1f s", total_run); + } + + fprintf(stderr, + "progress: %s, %.1f tps, lat %.3f ms stddev %.3f, " INT64_FORMAT " failed", + tbuf, tps, latency, stdev, failures); + + if (throttle_delay) + { + fprintf(stderr, ", lag %.3f ms", lag); + if (latency_limit) + fprintf(stderr, ", " INT64_FORMAT " skipped", + cur.skipped - last->skipped); + } + + /* it can be non-zero only if max_tries is not equal to one */ + if (max_tries != 1) + fprintf(stderr, + ", " INT64_FORMAT " retried, " INT64_FORMAT " retries", + retried, cur.retries - last->retries); + fprintf(stderr, "\n"); + + *last = cur; + *last_report = now; +} + +static void +printSimpleStats(const char *prefix, SimpleStats *ss) +{ + if (ss->count > 0) + { + double latency = ss->sum / ss->count; + double stddev = sqrt(ss->sum2 / ss->count - latency * latency); + + printf("%s average = %.3f ms\n", prefix, 0.001 * latency); + printf("%s stddev = %.3f ms\n", prefix, 0.001 * stddev); + } +} + +/* print version banner */ +static void +printVersion(PGconn *con) +{ + int server_ver = PQserverVersion(con); + int client_ver = PG_VERSION_NUM; + + if (server_ver != client_ver) + { + const char *server_version; + char sverbuf[32]; + + /* Try to get full text form, might include "devel" etc */ + server_version = PQparameterStatus(con, "server_version"); + /* Otherwise fall back on server_ver */ + if (!server_version) + { + formatPGVersionNumber(server_ver, true, + sverbuf, sizeof(sverbuf)); + server_version = sverbuf; + } + + printf(_("%s (%s, server %s)\n"), + "pgbench", PG_VERSION, server_version); + } + /* For version match, only print pgbench version */ + else + printf("%s (%s)\n", "pgbench", PG_VERSION); + fflush(stdout); +} + +/* print out results */ +static void +printResults(StatsData *total, + pg_time_usec_t total_duration, /* benchmarking time */ + pg_time_usec_t conn_total_duration, /* is_connect */ + pg_time_usec_t conn_elapsed_duration, /* !is_connect */ + int64 latency_late) +{ + /* tps is about actually executed transactions during benchmarking */ + int64 failures = getFailures(total); + int64 total_cnt = total->cnt + total->skipped + failures; + double bench_duration = PG_TIME_GET_DOUBLE(total_duration); + double tps = total->cnt / bench_duration; + + /* Report test parameters. */ + printf("transaction type: %s\n", + num_scripts == 1 ? sql_script[0].desc : "multiple scripts"); + printf("scaling factor: %d\n", scale); + /* only print partitioning information if some partitioning was detected */ + if (partition_method != PART_NONE) + printf("partition method: %s\npartitions: %d\n", + PARTITION_METHOD[partition_method], partitions); + printf("query mode: %s\n", QUERYMODE[querymode]); + printf("number of clients: %d\n", nclients); + printf("number of threads: %d\n", nthreads); + + if (max_tries) + printf("maximum number of tries: %u\n", max_tries); + + if (duration <= 0) + { + printf("number of transactions per client: %d\n", nxacts); + printf("number of transactions actually processed: " INT64_FORMAT "/%d\n", + total->cnt, nxacts * nclients); + } + else + { + printf("duration: %d s\n", duration); + printf("number of transactions actually processed: " INT64_FORMAT "\n", + total->cnt); + } + + printf("number of failed transactions: " INT64_FORMAT " (%.3f%%)\n", + failures, 100.0 * failures / total_cnt); + + if (failures_detailed) + { + printf("number of serialization failures: " INT64_FORMAT " (%.3f%%)\n", + total->serialization_failures, + 100.0 * total->serialization_failures / total_cnt); + printf("number of deadlock failures: " INT64_FORMAT " (%.3f%%)\n", + total->deadlock_failures, + 100.0 * total->deadlock_failures / total_cnt); + } + + /* it can be non-zero only if max_tries is not equal to one */ + if (max_tries != 1) + { + printf("number of transactions retried: " INT64_FORMAT " (%.3f%%)\n", + total->retried, 100.0 * total->retried / total_cnt); + printf("total number of retries: " INT64_FORMAT "\n", total->retries); + } + + /* Remaining stats are nonsensical if we failed to execute any xacts */ + if (total->cnt + total->skipped <= 0) + return; + + if (throttle_delay && latency_limit) + printf("number of transactions skipped: " INT64_FORMAT " (%.3f%%)\n", + total->skipped, 100.0 * total->skipped / total_cnt); + + if (latency_limit) + printf("number of transactions above the %.1f ms latency limit: " INT64_FORMAT "/" INT64_FORMAT " (%.3f%%)\n", + latency_limit / 1000.0, latency_late, total->cnt, + (total->cnt > 0) ? 100.0 * latency_late / total->cnt : 0.0); + + if (throttle_delay || progress || latency_limit) + printSimpleStats("latency", &total->latency); + else + { + /* no measurement, show average latency computed from run time */ + printf("latency average = %.3f ms%s\n", + 0.001 * total_duration * nclients / total_cnt, + failures > 0 ? " (including failures)" : ""); + } + + if (throttle_delay) + { + /* + * Report average transaction lag under rate limit throttling. This + * is the delay between scheduled and actual start times for the + * transaction. The measured lag may be caused by thread/client load, + * the database load, or the Poisson throttling process. + */ + printf("rate limit schedule lag: avg %.3f (max %.3f) ms\n", + 0.001 * total->lag.sum / total->cnt, 0.001 * total->lag.max); + } + + /* + * Under -C/--connect, each transaction incurs a significant connection + * cost, it would not make much sense to ignore it in tps, and it would + * not be tps anyway. + * + * Otherwise connections are made just once at the beginning of the run + * and should not impact performance but for very short run, so they are + * (right)fully ignored in tps. + */ + if (is_connect) + { + printf("average connection time = %.3f ms\n", 0.001 * conn_total_duration / (total->cnt + failures)); + printf("tps = %f (including reconnection times)\n", tps); + } + else + { + printf("initial connection time = %.3f ms\n", 0.001 * conn_elapsed_duration); + printf("tps = %f (without initial connection time)\n", tps); + } + + /* Report per-script/command statistics */ + if (per_script_stats || report_per_command) + { + int i; + + for (i = 0; i < num_scripts; i++) + { + if (per_script_stats) + { + StatsData *sstats = &sql_script[i].stats; + int64 script_failures = getFailures(sstats); + int64 script_total_cnt = + sstats->cnt + sstats->skipped + script_failures; + + printf("SQL script %d: %s\n" + " - weight: %d (targets %.1f%% of total)\n" + " - " INT64_FORMAT " transactions (%.1f%% of total, tps = %f)\n", + i + 1, sql_script[i].desc, + sql_script[i].weight, + 100.0 * sql_script[i].weight / total_weight, + sstats->cnt, + 100.0 * sstats->cnt / total->cnt, + sstats->cnt / bench_duration); + + printf(" - number of failed transactions: " INT64_FORMAT " (%.3f%%)\n", + script_failures, + 100.0 * script_failures / script_total_cnt); + + if (failures_detailed) + { + printf(" - number of serialization failures: " INT64_FORMAT " (%.3f%%)\n", + sstats->serialization_failures, + (100.0 * sstats->serialization_failures / + script_total_cnt)); + printf(" - number of deadlock failures: " INT64_FORMAT " (%.3f%%)\n", + sstats->deadlock_failures, + (100.0 * sstats->deadlock_failures / + script_total_cnt)); + } + + /* it can be non-zero only if max_tries is not equal to one */ + if (max_tries != 1) + { + printf(" - number of transactions retried: " INT64_FORMAT " (%.3f%%)\n", + sstats->retried, + 100.0 * sstats->retried / script_total_cnt); + printf(" - total number of retries: " INT64_FORMAT "\n", + sstats->retries); + } + + if (throttle_delay && latency_limit && script_total_cnt > 0) + printf(" - number of transactions skipped: " INT64_FORMAT " (%.3f%%)\n", + sstats->skipped, + 100.0 * sstats->skipped / script_total_cnt); + + printSimpleStats(" - latency", &sstats->latency); + } + + /* + * Report per-command statistics: latencies, retries after errors, + * failures (errors without retrying). + */ + if (report_per_command) + { + Command **commands; + + printf("%sstatement latencies in milliseconds%s:\n", + per_script_stats ? " - " : "", + (max_tries == 1 ? + " and failures" : + ", failures and retries")); + + for (commands = sql_script[i].commands; + *commands != NULL; + commands++) + { + SimpleStats *cstats = &(*commands)->stats; + + if (max_tries == 1) + printf(" %11.3f %10" INT64_MODIFIER "d %s\n", + (cstats->count > 0) ? + 1000.0 * cstats->sum / cstats->count : 0.0, + (*commands)->failures, + (*commands)->first_line); + else + printf(" %11.3f %10" INT64_MODIFIER "d %10" INT64_MODIFIER "d %s\n", + (cstats->count > 0) ? + 1000.0 * cstats->sum / cstats->count : 0.0, + (*commands)->failures, + (*commands)->retries, + (*commands)->first_line); + } + } + } + } +} + +/* + * Set up a random seed according to seed parameter (NULL means default), + * and initialize base_random_sequence for use in initializing other sequences. + */ +static bool +set_random_seed(const char *seed) +{ + uint64 iseed; + + if (seed == NULL || strcmp(seed, "time") == 0) + { + /* rely on current time */ + iseed = pg_time_now(); + } + else if (strcmp(seed, "rand") == 0) + { + /* use some "strong" random source */ + if (!pg_strong_random(&iseed, sizeof(iseed))) + { + pg_log_error("could not generate random seed"); + return false; + } + } + else + { + /* parse unsigned-int seed value */ + unsigned long ulseed; + char garbage; + + /* Don't try to use UINT64_FORMAT here; it might not work for sscanf */ + if (sscanf(seed, "%lu%c", &ulseed, &garbage) != 1) + { + pg_log_error("unrecognized random seed option \"%s\"", seed); + pg_log_error_detail("Expecting an unsigned integer, \"time\" or \"rand\"."); + return false; + } + iseed = (uint64) ulseed; + } + + if (seed != NULL) + pg_log_info("setting random seed to %llu", (unsigned long long) iseed); + + random_seed = iseed; + + /* Initialize base_random_sequence using seed */ + pg_prng_seed(&base_random_sequence, (uint64) iseed); + + return true; +} + +int +main(int argc, char **argv) +{ + static struct option long_options[] = { + /* systematic long/short named options */ + {"builtin", required_argument, NULL, 'b'}, + {"client", required_argument, NULL, 'c'}, + {"connect", no_argument, NULL, 'C'}, + {"debug", no_argument, NULL, 'd'}, + {"define", required_argument, NULL, 'D'}, + {"file", required_argument, NULL, 'f'}, + {"fillfactor", required_argument, NULL, 'F'}, + {"host", required_argument, NULL, 'h'}, + {"initialize", no_argument, NULL, 'i'}, + {"init-steps", required_argument, NULL, 'I'}, + {"jobs", required_argument, NULL, 'j'}, + {"log", no_argument, NULL, 'l'}, + {"latency-limit", required_argument, NULL, 'L'}, + {"no-vacuum", no_argument, NULL, 'n'}, + {"port", required_argument, NULL, 'p'}, + {"progress", required_argument, NULL, 'P'}, + {"protocol", required_argument, NULL, 'M'}, + {"quiet", no_argument, NULL, 'q'}, + {"report-per-command", no_argument, NULL, 'r'}, + {"rate", required_argument, NULL, 'R'}, + {"scale", required_argument, NULL, 's'}, + {"select-only", no_argument, NULL, 'S'}, + {"skip-some-updates", no_argument, NULL, 'N'}, + {"time", required_argument, NULL, 'T'}, + {"transactions", required_argument, NULL, 't'}, + {"username", required_argument, NULL, 'U'}, + {"vacuum-all", no_argument, NULL, 'v'}, + /* long-named only options */ + {"unlogged-tables", no_argument, NULL, 1}, + {"tablespace", required_argument, NULL, 2}, + {"index-tablespace", required_argument, NULL, 3}, + {"sampling-rate", required_argument, NULL, 4}, + {"aggregate-interval", required_argument, NULL, 5}, + {"progress-timestamp", no_argument, NULL, 6}, + {"log-prefix", required_argument, NULL, 7}, + {"foreign-keys", no_argument, NULL, 8}, + {"random-seed", required_argument, NULL, 9}, + {"show-script", required_argument, NULL, 10}, + {"partitions", required_argument, NULL, 11}, + {"partition-method", required_argument, NULL, 12}, + {"failures-detailed", no_argument, NULL, 13}, + {"max-tries", required_argument, NULL, 14}, + {"verbose-errors", no_argument, NULL, 15}, + {NULL, 0, NULL, 0} + }; + + int c; + bool is_init_mode = false; /* initialize mode? */ + char *initialize_steps = NULL; + bool foreign_keys = false; + bool is_no_vacuum = false; + bool do_vacuum_accounts = false; /* vacuum accounts table? */ + int optindex; + bool scale_given = false; + + bool benchmarking_option_set = false; + bool initialization_option_set = false; + bool internal_script_used = false; + + CState *state; /* status of clients */ + TState *threads; /* array of thread */ + + pg_time_usec_t + start_time, /* start up time */ + bench_start = 0, /* first recorded benchmarking time */ + conn_total_duration; /* cumulated connection time in + * threads */ + int64 latency_late = 0; + StatsData stats; + int weight; + + int i; + int nclients_dealt; + +#ifdef HAVE_GETRLIMIT + struct rlimit rlim; +#endif + + PGconn *con; + char *env; + + int exit_code = 0; + struct timeval tv; + + /* + * Record difference between Unix time and instr_time time. We'll use + * this for logging and aggregation. + */ + gettimeofday(&tv, NULL); + epoch_shift = tv.tv_sec * INT64CONST(1000000) + tv.tv_usec - pg_time_now(); + + pg_logging_init(argv[0]); + progname = get_progname(argv[0]); + + if (argc > 1) + { + if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) + { + usage(); + exit(0); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + puts("pgbench (PostgreSQL) " PG_VERSION); + exit(0); + } + } + + state = (CState *) pg_malloc0(sizeof(CState)); + + /* set random seed early, because it may be used while parsing scripts. */ + if (!set_random_seed(getenv("PGBENCH_RANDOM_SEED"))) + pg_fatal("error while setting random seed from PGBENCH_RANDOM_SEED environment variable"); + + while ((c = getopt_long(argc, argv, "iI:h:nvp:dqb:SNc:j:Crs:t:T:U:lf:D:F:M:P:R:L:", long_options, &optindex)) != -1) + { + char *script; + + switch (c) + { + case 'i': + is_init_mode = true; + break; + case 'I': + if (initialize_steps) + pg_free(initialize_steps); + initialize_steps = pg_strdup(optarg); + checkInitSteps(initialize_steps); + initialization_option_set = true; + break; + case 'h': + pghost = pg_strdup(optarg); + break; + case 'n': + is_no_vacuum = true; + break; + case 'v': + benchmarking_option_set = true; + do_vacuum_accounts = true; + break; + case 'p': + pgport = pg_strdup(optarg); + break; + case 'd': + pg_logging_increase_verbosity(); + break; + case 'c': + benchmarking_option_set = true; + if (!option_parse_int(optarg, "-c/--clients", 1, INT_MAX, + &nclients)) + { + exit(1); + } +#ifdef HAVE_GETRLIMIT +#ifdef RLIMIT_NOFILE /* most platforms use RLIMIT_NOFILE */ + if (getrlimit(RLIMIT_NOFILE, &rlim) == -1) +#else /* but BSD doesn't ... */ + if (getrlimit(RLIMIT_OFILE, &rlim) == -1) +#endif /* RLIMIT_NOFILE */ + pg_fatal("getrlimit failed: %m"); + if (rlim.rlim_cur < nclients + 3) + { + pg_log_error("need at least %d open files, but system limit is %ld", + nclients + 3, (long) rlim.rlim_cur); + pg_log_error_hint("Reduce number of clients, or use limit/ulimit to increase the system limit."); + exit(1); + } +#endif /* HAVE_GETRLIMIT */ + break; + case 'j': /* jobs */ + benchmarking_option_set = true; + if (!option_parse_int(optarg, "-j/--jobs", 1, INT_MAX, + &nthreads)) + { + exit(1); + } +#ifndef ENABLE_THREAD_SAFETY + if (nthreads != 1) + pg_fatal("threads are not supported on this platform; use -j1"); +#endif /* !ENABLE_THREAD_SAFETY */ + break; + case 'C': + benchmarking_option_set = true; + is_connect = true; + break; + case 'r': + benchmarking_option_set = true; + report_per_command = true; + break; + case 's': + scale_given = true; + if (!option_parse_int(optarg, "-s/--scale", 1, INT_MAX, + &scale)) + exit(1); + break; + case 't': + benchmarking_option_set = true; + if (!option_parse_int(optarg, "-t/--transactions", 1, INT_MAX, + &nxacts)) + exit(1); + break; + case 'T': + benchmarking_option_set = true; + if (!option_parse_int(optarg, "-T/--time", 1, INT_MAX, + &duration)) + exit(1); + break; + case 'U': + username = pg_strdup(optarg); + break; + case 'l': + benchmarking_option_set = true; + use_log = true; + break; + case 'q': + initialization_option_set = true; + use_quiet = true; + break; + case 'b': + if (strcmp(optarg, "list") == 0) + { + listAvailableScripts(); + exit(0); + } + weight = parseScriptWeight(optarg, &script); + process_builtin(findBuiltin(script), weight); + benchmarking_option_set = true; + internal_script_used = true; + break; + case 'S': + process_builtin(findBuiltin("select-only"), 1); + benchmarking_option_set = true; + internal_script_used = true; + break; + case 'N': + process_builtin(findBuiltin("simple-update"), 1); + benchmarking_option_set = true; + internal_script_used = true; + break; + case 'f': + weight = parseScriptWeight(optarg, &script); + process_file(script, weight); + benchmarking_option_set = true; + break; + case 'D': + { + char *p; + + benchmarking_option_set = true; + + if ((p = strchr(optarg, '=')) == NULL || p == optarg || *(p + 1) == '\0') + pg_fatal("invalid variable definition: \"%s\"", optarg); + + *p++ = '\0'; + if (!putVariable(&state[0].variables, "option", optarg, p)) + exit(1); + } + break; + case 'F': + initialization_option_set = true; + if (!option_parse_int(optarg, "-F/--fillfactor", 10, 100, + &fillfactor)) + exit(1); + break; + case 'M': + benchmarking_option_set = true; + for (querymode = 0; querymode < NUM_QUERYMODE; querymode++) + if (strcmp(optarg, QUERYMODE[querymode]) == 0) + break; + if (querymode >= NUM_QUERYMODE) + pg_fatal("invalid query mode (-M): \"%s\"", optarg); + break; + case 'P': + benchmarking_option_set = true; + if (!option_parse_int(optarg, "-P/--progress", 1, INT_MAX, + &progress)) + exit(1); + break; + case 'R': + { + /* get a double from the beginning of option value */ + double throttle_value = atof(optarg); + + benchmarking_option_set = true; + + if (throttle_value <= 0.0) + pg_fatal("invalid rate limit: \"%s\"", optarg); + /* Invert rate limit into per-transaction delay in usec */ + throttle_delay = 1000000.0 / throttle_value; + } + break; + case 'L': + { + double limit_ms = atof(optarg); + + if (limit_ms <= 0.0) + pg_fatal("invalid latency limit: \"%s\"", optarg); + benchmarking_option_set = true; + latency_limit = (int64) (limit_ms * 1000); + } + break; + case 1: /* unlogged-tables */ + initialization_option_set = true; + unlogged_tables = true; + break; + case 2: /* tablespace */ + initialization_option_set = true; + tablespace = pg_strdup(optarg); + break; + case 3: /* index-tablespace */ + initialization_option_set = true; + index_tablespace = pg_strdup(optarg); + break; + case 4: /* sampling-rate */ + benchmarking_option_set = true; + sample_rate = atof(optarg); + if (sample_rate <= 0.0 || sample_rate > 1.0) + pg_fatal("invalid sampling rate: \"%s\"", optarg); + break; + case 5: /* aggregate-interval */ + benchmarking_option_set = true; + if (!option_parse_int(optarg, "--aggregate-interval", 1, INT_MAX, + &agg_interval)) + exit(1); + break; + case 6: /* progress-timestamp */ + progress_timestamp = true; + benchmarking_option_set = true; + break; + case 7: /* log-prefix */ + benchmarking_option_set = true; + logfile_prefix = pg_strdup(optarg); + break; + case 8: /* foreign-keys */ + initialization_option_set = true; + foreign_keys = true; + break; + case 9: /* random-seed */ + benchmarking_option_set = true; + if (!set_random_seed(optarg)) + pg_fatal("error while setting random seed from --random-seed option"); + break; + case 10: /* list */ + { + const BuiltinScript *s = findBuiltin(optarg); + + fprintf(stderr, "-- %s: %s\n%s\n", s->name, s->desc, s->script); + exit(0); + } + break; + case 11: /* partitions */ + initialization_option_set = true; + if (!option_parse_int(optarg, "--partitions", 0, INT_MAX, + &partitions)) + exit(1); + break; + case 12: /* partition-method */ + initialization_option_set = true; + if (pg_strcasecmp(optarg, "range") == 0) + partition_method = PART_RANGE; + else if (pg_strcasecmp(optarg, "hash") == 0) + partition_method = PART_HASH; + else + pg_fatal("invalid partition method, expecting \"range\" or \"hash\", got: \"%s\"", + optarg); + break; + case 13: /* failures-detailed */ + benchmarking_option_set = true; + failures_detailed = true; + break; + case 14: /* max-tries */ + { + int32 max_tries_arg = atoi(optarg); + + if (max_tries_arg < 0) + pg_fatal("invalid number of maximum tries: \"%s\"", optarg); + + benchmarking_option_set = true; + max_tries = (uint32) max_tries_arg; + } + break; + case 15: /* verbose-errors */ + benchmarking_option_set = true; + verbose_errors = true; + break; + default: + /* getopt_long already emitted a complaint */ + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } + } + + /* set default script if none */ + if (num_scripts == 0 && !is_init_mode) + { + process_builtin(findBuiltin("tpcb-like"), 1); + benchmarking_option_set = true; + internal_script_used = true; + } + + /* complete SQL command initialization and compute total weight */ + for (i = 0; i < num_scripts; i++) + { + Command **commands = sql_script[i].commands; + + for (int j = 0; commands[j] != NULL; j++) + if (commands[j]->type == SQL_COMMAND) + postprocess_sql_command(commands[j]); + + /* cannot overflow: weight is 32b, total_weight 64b */ + total_weight += sql_script[i].weight; + } + + if (total_weight == 0 && !is_init_mode) + pg_fatal("total script weight must not be zero"); + + /* show per script stats if several scripts are used */ + if (num_scripts > 1) + per_script_stats = true; + + /* + * Don't need more threads than there are clients. (This is not merely an + * optimization; throttle_delay is calculated incorrectly below if some + * threads have no clients assigned to them.) + */ + if (nthreads > nclients) + nthreads = nclients; + + /* + * Convert throttle_delay to a per-thread delay time. Note that this + * might be a fractional number of usec, but that's OK, since it's just + * the center of a Poisson distribution of delays. + */ + throttle_delay *= nthreads; + + if (argc > optind) + dbName = argv[optind++]; + else + { + if ((env = getenv("PGDATABASE")) != NULL && *env != '\0') + dbName = env; + else if ((env = getenv("PGUSER")) != NULL && *env != '\0') + dbName = env; + else + dbName = get_user_name_or_exit(progname); + } + + if (optind < argc) + { + pg_log_error("too many command-line arguments (first is \"%s\")", + argv[optind]); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } + + if (is_init_mode) + { + if (benchmarking_option_set) + pg_fatal("some of the specified options cannot be used in initialization (-i) mode"); + + if (partitions == 0 && partition_method != PART_NONE) + pg_fatal("--partition-method requires greater than zero --partitions"); + + /* set default method */ + if (partitions > 0 && partition_method == PART_NONE) + partition_method = PART_RANGE; + + if (initialize_steps == NULL) + initialize_steps = pg_strdup(DEFAULT_INIT_STEPS); + + if (is_no_vacuum) + { + /* Remove any vacuum step in initialize_steps */ + char *p; + + while ((p = strchr(initialize_steps, 'v')) != NULL) + *p = ' '; + } + + if (foreign_keys) + { + /* Add 'f' to end of initialize_steps, if not already there */ + if (strchr(initialize_steps, 'f') == NULL) + { + initialize_steps = (char *) + pg_realloc(initialize_steps, + strlen(initialize_steps) + 2); + strcat(initialize_steps, "f"); + } + } + + runInitSteps(initialize_steps); + exit(0); + } + else + { + if (initialization_option_set) + pg_fatal("some of the specified options cannot be used in benchmarking mode"); + } + + if (nxacts > 0 && duration > 0) + pg_fatal("specify either a number of transactions (-t) or a duration (-T), not both"); + + /* Use DEFAULT_NXACTS if neither nxacts nor duration is specified. */ + if (nxacts <= 0 && duration <= 0) + nxacts = DEFAULT_NXACTS; + + /* --sampling-rate may be used only with -l */ + if (sample_rate > 0.0 && !use_log) + pg_fatal("log sampling (--sampling-rate) is allowed only when logging transactions (-l)"); + + /* --sampling-rate may not be used with --aggregate-interval */ + if (sample_rate > 0.0 && agg_interval > 0) + pg_fatal("log sampling (--sampling-rate) and aggregation (--aggregate-interval) cannot be used at the same time"); + + if (agg_interval > 0 && !use_log) + pg_fatal("log aggregation is allowed only when actually logging transactions"); + + if (!use_log && logfile_prefix) + pg_fatal("log file prefix (--log-prefix) is allowed only when logging transactions (-l)"); + + if (duration > 0 && agg_interval > duration) + pg_fatal("number of seconds for aggregation (%d) must not be higher than test duration (%d)", agg_interval, duration); + + if (duration > 0 && agg_interval > 0 && duration % agg_interval != 0) + pg_fatal("duration (%d) must be a multiple of aggregation interval (%d)", duration, agg_interval); + + if (progress_timestamp && progress == 0) + pg_fatal("--progress-timestamp is allowed only under --progress"); + + if (!max_tries) + { + if (!latency_limit && duration <= 0) + pg_fatal("an unlimited number of transaction tries can only be used with --latency-limit or a duration (-T)"); + } + + /* + * save main process id in the global variable because process id will be + * changed after fork. + */ + main_pid = (int) getpid(); + + if (nclients > 1) + { + state = (CState *) pg_realloc(state, sizeof(CState) * nclients); + memset(state + 1, 0, sizeof(CState) * (nclients - 1)); + + /* copy any -D switch values to all clients */ + for (i = 1; i < nclients; i++) + { + int j; + + state[i].id = i; + for (j = 0; j < state[0].variables.nvars; j++) + { + Variable *var = &state[0].variables.vars[j]; + + if (var->value.type != PGBT_NO_VALUE) + { + if (!putVariableValue(&state[i].variables, "startup", + var->name, &var->value)) + exit(1); + } + else + { + if (!putVariable(&state[i].variables, "startup", + var->name, var->svalue)) + exit(1); + } + } + } + } + + /* other CState initializations */ + for (i = 0; i < nclients; i++) + { + state[i].cstack = conditional_stack_create(); + initRandomState(&state[i].cs_func_rs); + } + + /* opening connection... */ + con = doConnect(); + if (con == NULL) + pg_fatal("could not create connection for setup"); + + /* report pgbench and server versions */ + printVersion(con); + + pg_log_debug("pghost: %s pgport: %s nclients: %d %s: %d dbName: %s", + PQhost(con), PQport(con), nclients, + duration <= 0 ? "nxacts" : "duration", + duration <= 0 ? nxacts : duration, PQdb(con)); + + if (internal_script_used) + GetTableInfo(con, scale_given); + + /* + * :scale variables normally get -s or database scale, but don't override + * an explicit -D switch + */ + if (lookupVariable(&state[0].variables, "scale") == NULL) + { + for (i = 0; i < nclients; i++) + { + if (!putVariableInt(&state[i].variables, "startup", "scale", scale)) + exit(1); + } + } + + /* + * Define a :client_id variable that is unique per connection. But don't + * override an explicit -D switch. + */ + if (lookupVariable(&state[0].variables, "client_id") == NULL) + { + for (i = 0; i < nclients; i++) + if (!putVariableInt(&state[i].variables, "startup", "client_id", i)) + exit(1); + } + + /* set default seed for hash functions */ + if (lookupVariable(&state[0].variables, "default_seed") == NULL) + { + uint64 seed = pg_prng_uint64(&base_random_sequence); + + for (i = 0; i < nclients; i++) + if (!putVariableInt(&state[i].variables, "startup", "default_seed", + (int64) seed)) + exit(1); + } + + /* set random seed unless overwritten */ + if (lookupVariable(&state[0].variables, "random_seed") == NULL) + { + for (i = 0; i < nclients; i++) + if (!putVariableInt(&state[i].variables, "startup", "random_seed", + random_seed)) + exit(1); + } + + if (!is_no_vacuum) + { + fprintf(stderr, "starting vacuum..."); + tryExecuteStatement(con, "vacuum pgbench_branches"); + tryExecuteStatement(con, "vacuum pgbench_tellers"); + tryExecuteStatement(con, "truncate pgbench_history"); + fprintf(stderr, "end.\n"); + + if (do_vacuum_accounts) + { + fprintf(stderr, "starting vacuum pgbench_accounts..."); + tryExecuteStatement(con, "vacuum analyze pgbench_accounts"); + fprintf(stderr, "end.\n"); + } + } + PQfinish(con); + + /* set up thread data structures */ + threads = (TState *) pg_malloc(sizeof(TState) * nthreads); + nclients_dealt = 0; + + for (i = 0; i < nthreads; i++) + { + TState *thread = &threads[i]; + + thread->tid = i; + thread->state = &state[nclients_dealt]; + thread->nstate = + (nclients - nclients_dealt + nthreads - i - 1) / (nthreads - i); + initRandomState(&thread->ts_choose_rs); + initRandomState(&thread->ts_throttle_rs); + initRandomState(&thread->ts_sample_rs); + thread->logfile = NULL; /* filled in later */ + thread->latency_late = 0; + initStats(&thread->stats, 0); + + nclients_dealt += thread->nstate; + } + + /* all clients must be assigned to a thread */ + Assert(nclients_dealt == nclients); + + /* get start up time for the whole computation */ + start_time = pg_time_now(); + + /* set alarm if duration is specified. */ + if (duration > 0) + setalarm(duration); + + errno = THREAD_BARRIER_INIT(&barrier, nthreads); + if (errno != 0) + pg_fatal("could not initialize barrier: %m"); + +#ifdef ENABLE_THREAD_SAFETY + /* start all threads but thread 0 which is executed directly later */ + for (i = 1; i < nthreads; i++) + { + TState *thread = &threads[i]; + + thread->create_time = pg_time_now(); + errno = THREAD_CREATE(&thread->thread, threadRun, thread); + + if (errno != 0) + pg_fatal("could not create thread: %m"); + } +#else + Assert(nthreads == 1); +#endif /* ENABLE_THREAD_SAFETY */ + + /* compute when to stop */ + threads[0].create_time = pg_time_now(); + if (duration > 0) + end_time = threads[0].create_time + (int64) 1000000 * duration; + + /* run thread 0 directly */ + (void) threadRun(&threads[0]); + + /* wait for other threads and accumulate results */ + initStats(&stats, 0); + conn_total_duration = 0; + + for (i = 0; i < nthreads; i++) + { + TState *thread = &threads[i]; + +#ifdef ENABLE_THREAD_SAFETY + if (i > 0) + THREAD_JOIN(thread->thread); +#endif /* ENABLE_THREAD_SAFETY */ + + for (int j = 0; j < thread->nstate; j++) + if (thread->state[j].state != CSTATE_FINISHED) + exit_code = 2; + + /* aggregate thread level stats */ + mergeSimpleStats(&stats.latency, &thread->stats.latency); + mergeSimpleStats(&stats.lag, &thread->stats.lag); + stats.cnt += thread->stats.cnt; + stats.skipped += thread->stats.skipped; + stats.retries += thread->stats.retries; + stats.retried += thread->stats.retried; + stats.serialization_failures += thread->stats.serialization_failures; + stats.deadlock_failures += thread->stats.deadlock_failures; + latency_late += thread->latency_late; + conn_total_duration += thread->conn_duration; + + /* first recorded benchmarking start time */ + if (bench_start == 0 || thread->bench_start < bench_start) + bench_start = thread->bench_start; + } + + /* + * All connections should be already closed in threadRun(), so this + * disconnect_all() will be a no-op, but clean up the connections just to + * be sure. We don't need to measure the disconnection delays here. + */ + disconnect_all(state, nclients); + + /* + * Beware that performance of short benchmarks with many threads and + * possibly long transactions can be deceptive because threads do not + * start and finish at the exact same time. The total duration computed + * here encompasses all transactions so that tps shown is somehow slightly + * underestimated. + */ + printResults(&stats, pg_time_now() - bench_start, conn_total_duration, + bench_start - start_time, latency_late); + + THREAD_BARRIER_DESTROY(&barrier); + + if (exit_code != 0) + pg_log_error("Run was aborted; the above results are incomplete."); + + return exit_code; +} + +static THREAD_FUNC_RETURN_TYPE THREAD_FUNC_CC +threadRun(void *arg) +{ + TState *thread = (TState *) arg; + CState *state = thread->state; + pg_time_usec_t start; + int nstate = thread->nstate; + int remains = nstate; /* number of remaining clients */ + socket_set *sockets = alloc_socket_set(nstate); + int64 thread_start, + last_report, + next_report; + StatsData last, + aggs; + + /* open log file if requested */ + if (use_log) + { + char logpath[MAXPGPATH]; + char *prefix = logfile_prefix ? logfile_prefix : "pgbench_log"; + + if (thread->tid == 0) + snprintf(logpath, sizeof(logpath), "%s.%d", prefix, main_pid); + else + snprintf(logpath, sizeof(logpath), "%s.%d.%d", prefix, main_pid, thread->tid); + + thread->logfile = fopen(logpath, "w"); + + if (thread->logfile == NULL) + pg_fatal("could not open logfile \"%s\": %m", logpath); + } + + /* explicitly initialize the state machines */ + for (int i = 0; i < nstate; i++) + state[i].state = CSTATE_CHOOSE_SCRIPT; + + /* READY */ + THREAD_BARRIER_WAIT(&barrier); + + thread_start = pg_time_now(); + thread->started_time = thread_start; + thread->conn_duration = 0; + last_report = thread_start; + next_report = last_report + (int64) 1000000 * progress; + + /* STEADY */ + if (!is_connect) + { + /* make connections to the database before starting */ + for (int i = 0; i < nstate; i++) + { + if ((state[i].con = doConnect()) == NULL) + { + /* coldly abort on initial connection failure */ + pg_fatal("could not create connection for client %d", + state[i].id); + } + } + } + + /* GO */ + THREAD_BARRIER_WAIT(&barrier); + + start = pg_time_now(); + thread->bench_start = start; + thread->throttle_trigger = start; + + /* + * The log format currently has Unix epoch timestamps with whole numbers + * of seconds. Round the first aggregate's start time down to the nearest + * Unix epoch second (the very first aggregate might really have started a + * fraction of a second later, but later aggregates are measured from the + * whole number time that is actually logged). + */ + initStats(&aggs, (start + epoch_shift) / 1000000 * 1000000); + last = aggs; + + /* loop till all clients have terminated */ + while (remains > 0) + { + int nsocks; /* number of sockets to be waited for */ + pg_time_usec_t min_usec; + pg_time_usec_t now = 0; /* set this only if needed */ + + /* + * identify which client sockets should be checked for input, and + * compute the nearest time (if any) at which we need to wake up. + */ + clear_socket_set(sockets); + nsocks = 0; + min_usec = PG_INT64_MAX; + for (int i = 0; i < nstate; i++) + { + CState *st = &state[i]; + + if (st->state == CSTATE_SLEEP || st->state == CSTATE_THROTTLE) + { + /* a nap from the script, or under throttling */ + pg_time_usec_t this_usec; + + /* get current time if needed */ + pg_time_now_lazy(&now); + + /* min_usec should be the minimum delay across all clients */ + this_usec = (st->state == CSTATE_SLEEP ? + st->sleep_until : st->txn_scheduled) - now; + if (min_usec > this_usec) + min_usec = this_usec; + } + else if (st->state == CSTATE_WAIT_RESULT || + st->state == CSTATE_WAIT_ROLLBACK_RESULT) + { + /* + * waiting for result from server - nothing to do unless the + * socket is readable + */ + int sock = PQsocket(st->con); + + if (sock < 0) + { + pg_log_error("invalid socket: %s", PQerrorMessage(st->con)); + goto done; + } + + add_socket_to_set(sockets, sock, nsocks++); + } + else if (st->state != CSTATE_ABORTED && + st->state != CSTATE_FINISHED) + { + /* + * This client thread is ready to do something, so we don't + * want to wait. No need to examine additional clients. + */ + min_usec = 0; + break; + } + } + + /* also wake up to print the next progress report on time */ + if (progress && min_usec > 0 && thread->tid == 0) + { + pg_time_now_lazy(&now); + + if (now >= next_report) + min_usec = 0; + else if ((next_report - now) < min_usec) + min_usec = next_report - now; + } + + /* + * If no clients are ready to execute actions, sleep until we receive + * data on some client socket or the timeout (if any) elapses. + */ + if (min_usec > 0) + { + int rc = 0; + + if (min_usec != PG_INT64_MAX) + { + if (nsocks > 0) + { + rc = wait_on_socket_set(sockets, min_usec); + } + else /* nothing active, simple sleep */ + { + pg_usleep(min_usec); + } + } + else /* no explicit delay, wait without timeout */ + { + rc = wait_on_socket_set(sockets, 0); + } + + if (rc < 0) + { + if (errno == EINTR) + { + /* On EINTR, go back to top of loop */ + continue; + } + /* must be something wrong */ + pg_log_error("%s() failed: %m", SOCKET_WAIT_METHOD); + goto done; + } + } + else + { + /* min_usec <= 0, i.e. something needs to be executed now */ + + /* If we didn't wait, don't try to read any data */ + clear_socket_set(sockets); + } + + /* ok, advance the state machine of each connection */ + nsocks = 0; + for (int i = 0; i < nstate; i++) + { + CState *st = &state[i]; + + if (st->state == CSTATE_WAIT_RESULT || + st->state == CSTATE_WAIT_ROLLBACK_RESULT) + { + /* don't call advanceConnectionState unless data is available */ + int sock = PQsocket(st->con); + + if (sock < 0) + { + pg_log_error("invalid socket: %s", PQerrorMessage(st->con)); + goto done; + } + + if (!socket_has_input(sockets, sock, nsocks++)) + continue; + } + else if (st->state == CSTATE_FINISHED || + st->state == CSTATE_ABORTED) + { + /* this client is done, no need to consider it anymore */ + continue; + } + + advanceConnectionState(thread, st, &aggs); + + /* + * If advanceConnectionState changed client to finished state, + * that's one fewer client that remains. + */ + if (st->state == CSTATE_FINISHED || st->state == CSTATE_ABORTED) + remains--; + } + + /* progress report is made by thread 0 for all threads */ + if (progress && thread->tid == 0) + { + pg_time_usec_t now = pg_time_now(); + + if (now >= next_report) + { + /* + * Horrible hack: this relies on the thread pointer we are + * passed to be equivalent to threads[0], that is the first + * entry of the threads array. That is why this MUST be done + * by thread 0 and not any other. + */ + printProgressReport(thread, thread_start, now, + &last, &last_report); + + /* + * Ensure that the next report is in the future, in case + * pgbench/postgres got stuck somewhere. + */ + do + { + next_report += (int64) 1000000 * progress; + } while (now >= next_report); + } + } + } + +done: + disconnect_all(state, nstate); + + if (thread->logfile) + { + if (agg_interval > 0) + { + /* log aggregated but not yet reported transactions */ + doLog(thread, state, &aggs, false, 0, 0); + } + fclose(thread->logfile); + thread->logfile = NULL; + } + free_socket_set(sockets); + THREAD_FUNC_RETURN; +} + +static void +finishCon(CState *st) +{ + if (st->con != NULL) + { + PQfinish(st->con); + st->con = NULL; + } +} + +/* + * Support for duration option: set timer_exceeded after so many seconds. + */ + +#ifndef WIN32 + +static void +handle_sig_alarm(SIGNAL_ARGS) +{ + timer_exceeded = true; +} + +static void +setalarm(int seconds) +{ + pqsignal(SIGALRM, handle_sig_alarm); + alarm(seconds); +} + +#else /* WIN32 */ + +static VOID CALLBACK +win32_timer_callback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) +{ + timer_exceeded = true; +} + +static void +setalarm(int seconds) +{ + HANDLE queue; + HANDLE timer; + + /* This function will be called at most once, so we can cheat a bit. */ + queue = CreateTimerQueue(); + if (seconds > ((DWORD) -1) / 1000 || + !CreateTimerQueueTimer(&timer, queue, + win32_timer_callback, NULL, seconds * 1000, 0, + WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) + pg_fatal("failed to set timer"); +} + +#endif /* WIN32 */ + + +/* + * These functions provide an abstraction layer that hides the syscall + * we use to wait for input on a set of sockets. + * + * Currently there are two implementations, based on ppoll(2) and select(2). + * ppoll() is preferred where available due to its typically higher ceiling + * on the number of usable sockets. We do not use the more-widely-available + * poll(2) because it only offers millisecond timeout resolution, which could + * be problematic with high --rate settings. + * + * Function APIs: + * + * alloc_socket_set: allocate an empty socket set with room for up to + * "count" sockets. + * + * free_socket_set: deallocate a socket set. + * + * clear_socket_set: reset a socket set to empty. + * + * add_socket_to_set: add socket with indicated FD to slot "idx" in the + * socket set. Slots must be filled in order, starting with 0. + * + * wait_on_socket_set: wait for input on any socket in set, or for timeout + * to expire. timeout is measured in microseconds; 0 means wait forever. + * Returns result code of underlying syscall (>=0 if OK, else see errno). + * + * socket_has_input: after waiting, call this to see if given socket has + * input. fd and idx parameters should match some previous call to + * add_socket_to_set. + * + * Note that wait_on_socket_set destructively modifies the state of the + * socket set. After checking for input, caller must apply clear_socket_set + * and add_socket_to_set again before waiting again. + */ + +#ifdef POLL_USING_PPOLL + +static socket_set * +alloc_socket_set(int count) +{ + socket_set *sa; + + sa = (socket_set *) pg_malloc0(offsetof(socket_set, pollfds) + + sizeof(struct pollfd) * count); + sa->maxfds = count; + sa->curfds = 0; + return sa; +} + +static void +free_socket_set(socket_set *sa) +{ + pg_free(sa); +} + +static void +clear_socket_set(socket_set *sa) +{ + sa->curfds = 0; +} + +static void +add_socket_to_set(socket_set *sa, int fd, int idx) +{ + Assert(idx < sa->maxfds && idx == sa->curfds); + sa->pollfds[idx].fd = fd; + sa->pollfds[idx].events = POLLIN; + sa->pollfds[idx].revents = 0; + sa->curfds++; +} + +static int +wait_on_socket_set(socket_set *sa, int64 usecs) +{ + if (usecs > 0) + { + struct timespec timeout; + + timeout.tv_sec = usecs / 1000000; + timeout.tv_nsec = (usecs % 1000000) * 1000; + return ppoll(sa->pollfds, sa->curfds, &timeout, NULL); + } + else + { + return ppoll(sa->pollfds, sa->curfds, NULL, NULL); + } +} + +static bool +socket_has_input(socket_set *sa, int fd, int idx) +{ + /* + * In some cases, threadRun will apply clear_socket_set and then try to + * apply socket_has_input anyway with arguments that it used before that, + * or might've used before that except that it exited its setup loop + * early. Hence, if the socket set is empty, silently return false + * regardless of the parameters. If it's not empty, we can Assert that + * the parameters match a previous call. + */ + if (sa->curfds == 0) + return false; + + Assert(idx < sa->curfds && sa->pollfds[idx].fd == fd); + return (sa->pollfds[idx].revents & POLLIN) != 0; +} + +#endif /* POLL_USING_PPOLL */ + +#ifdef POLL_USING_SELECT + +static socket_set * +alloc_socket_set(int count) +{ + return (socket_set *) pg_malloc0(sizeof(socket_set)); +} + +static void +free_socket_set(socket_set *sa) +{ + pg_free(sa); +} + +static void +clear_socket_set(socket_set *sa) +{ + FD_ZERO(&sa->fds); + sa->maxfd = -1; +} + +static void +add_socket_to_set(socket_set *sa, int fd, int idx) +{ + if (fd < 0 || fd >= FD_SETSIZE) + { + /* + * Doing a hard exit here is a bit grotty, but it doesn't seem worth + * complicating the API to make it less grotty. + */ + pg_fatal("too many client connections for select()"); + } + FD_SET(fd, &sa->fds); + if (fd > sa->maxfd) + sa->maxfd = fd; +} + +static int +wait_on_socket_set(socket_set *sa, int64 usecs) +{ + if (usecs > 0) + { + struct timeval timeout; + + timeout.tv_sec = usecs / 1000000; + timeout.tv_usec = usecs % 1000000; + return select(sa->maxfd + 1, &sa->fds, NULL, NULL, &timeout); + } + else + { + return select(sa->maxfd + 1, &sa->fds, NULL, NULL, NULL); + } +} + +static bool +socket_has_input(socket_set *sa, int fd, int idx) +{ + return (FD_ISSET(fd, &sa->fds) != 0); +} + +#endif /* POLL_USING_SELECT */ diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h new file mode 100644 index 0000000..abbdc44 --- /dev/null +++ b/src/bin/pgbench/pgbench.h @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------- + * + * pgbench.h + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + *------------------------------------------------------------------------- + */ + +#ifndef PGBENCH_H +#define PGBENCH_H + +#include "fe_utils/psqlscan.h" + +/* + * This file is included outside exprscan.l, in places where we can't see + * flex's definition of typedef yyscan_t. Fortunately, it's documented as + * being "void *", so we can use a macro to keep the function declarations + * here looking like the definitions in exprscan.l. exprparse.y and + * pgbench.c also use this to be able to declare things as "yyscan_t". + */ +#define yyscan_t void * + +/* + * Likewise, we can't see exprparse.y's definition of union YYSTYPE here, + * but for now there's no need to know what the union contents are. + */ +union YYSTYPE; + +/* + * Variable types used in parser. + */ +typedef enum +{ + PGBT_NO_VALUE, + PGBT_NULL, + PGBT_INT, + PGBT_DOUBLE, + PGBT_BOOLEAN + /* add other types here */ +} PgBenchValueType; + +typedef struct +{ + PgBenchValueType type; + union + { + int64 ival; + double dval; + bool bval; + /* add other types here */ + } u; +} PgBenchValue; + +/* Types of expression nodes */ +typedef enum PgBenchExprType +{ + ENODE_CONSTANT, + ENODE_VARIABLE, + ENODE_FUNCTION +} PgBenchExprType; + +/* List of operators and callable functions */ +typedef enum PgBenchFunction +{ + PGBENCH_ADD, + PGBENCH_SUB, + PGBENCH_MUL, + PGBENCH_DIV, + PGBENCH_MOD, + PGBENCH_DEBUG, + PGBENCH_ABS, + PGBENCH_LEAST, + PGBENCH_GREATEST, + PGBENCH_INT, + PGBENCH_DOUBLE, + PGBENCH_PI, + PGBENCH_SQRT, + PGBENCH_LN, + PGBENCH_EXP, + PGBENCH_RANDOM, + PGBENCH_RANDOM_GAUSSIAN, + PGBENCH_RANDOM_EXPONENTIAL, + PGBENCH_RANDOM_ZIPFIAN, + 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, + PGBENCH_HASH_FNV1A, + PGBENCH_HASH_MURMUR2, + PGBENCH_PERMUTE +} PgBenchFunction; + +typedef struct PgBenchExpr PgBenchExpr; +typedef struct PgBenchExprLink PgBenchExprLink; +typedef struct PgBenchExprList PgBenchExprList; + +struct PgBenchExpr +{ + PgBenchExprType etype; + union + { + PgBenchValue constant; + struct + { + char *varname; + } variable; + struct + { + PgBenchFunction function; + PgBenchExprLink *args; + } function; + } u; +}; + +/* List of expression nodes */ +struct PgBenchExprLink +{ + PgBenchExpr *expr; + PgBenchExprLink *next; +}; + +struct PgBenchExprList +{ + PgBenchExprLink *head; + PgBenchExprLink *tail; +}; + +extern PgBenchExpr *expr_parse_result; + +extern int expr_yyparse(yyscan_t yyscanner); +extern int expr_yylex(union YYSTYPE *lvalp, yyscan_t yyscanner); +extern void expr_yyerror(yyscan_t yyscanner, const char *str) pg_attribute_noreturn(); +extern void expr_yyerror_more(yyscan_t yyscanner, const char *str, + const char *more) pg_attribute_noreturn(); +extern bool expr_lex_one_word(PsqlScanState state, PQExpBuffer word_buf, + int *offset); +extern yyscan_t expr_scanner_init(PsqlScanState state, + const char *source, int lineno, int start_offset, + const char *command); +extern void expr_scanner_finish(yyscan_t yyscanner); +extern int expr_scanner_offset(PsqlScanState state); +extern char *expr_scanner_get_substring(PsqlScanState state, + int start_offset, int end_offset, + bool chomp); +extern int expr_scanner_get_lineno(PsqlScanState state, int offset); + +extern void syntax_error(const char *source, int lineno, const char *line, + const char *cmd, const char *msg, + const char *more, int col) pg_attribute_noreturn(); + +extern bool strtoint64(const char *str, bool errorOK, int64 *pi); +extern bool strtodouble(const char *str, bool errorOK, double *pd); + +#endif /* PGBENCH_H */ diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl new file mode 100644 index 0000000..027c513 --- /dev/null +++ b/src/bin/pgbench/t/001_pgbench_with_server.pl @@ -0,0 +1,1447 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +# start a pgbench specific server +my $node = PostgreSQL::Test::Cluster->new('main'); +# Set to untranslated messages, to be able to compare program output with +# expected strings. +$node->init(extra => [ '--locale', 'C' ]); +$node->start; + +# tablespace for testing, because partitioned tables cannot use pg_default +# explicitly and we want to test that table creation with tablespace works +# for partitioned tables. +my $ts = $node->basedir . '/regress_pgbench_tap_1_ts_dir'; +mkdir $ts or die "cannot create directory $ts"; + +# the next commands will issue a syntax error if the path contains a "'" +$node->safe_psql('postgres', + "CREATE TABLESPACE regress_pgbench_tap_1_ts LOCATION '$ts';"); + +# Test concurrent OID generation via pg_enum_oid_index. This indirectly +# exercises LWLock and spinlock concurrency. +my $labels = join ',', map { "'l$_'" } 1 .. 1000; +$node->pgbench( + '--no-vacuum --client=5 --protocol=prepared --transactions=25', + 0, + [qr{processed: 125/125}], + [qr{^$}], + 'concurrent OID generation', + { + '001_pgbench_concurrent_insert' => + "CREATE TYPE pg_temp.e AS ENUM ($labels); DROP TYPE pg_temp.e;" + }); + +# Trigger various connection errors +$node->pgbench( + 'no-such-database', + 1, + [qr{^$}], + [ + qr{connection to server .* failed}, + qr{FATAL: database "no-such-database" does not exist} + ], + 'no such database'); + +$node->pgbench( + '-S -t 1', 1, [], + [qr{Perhaps you need to do initialization}], + 'run without init'); + +# Initialize pgbench tables scale 1 +$node->pgbench( + '-i', 0, + [qr{^$}], + [ + qr{creating tables}, + qr{vacuuming}, + qr{creating primary keys}, + qr{done in \d+\.\d\d s } + ], + 'pgbench scale 1 initialization',); + +# Again, with all possible options +$node->pgbench( + '--initialize --init-steps=dtpvg --scale=1 --unlogged-tables --fillfactor=98 --foreign-keys --quiet --tablespace=regress_pgbench_tap_1_ts --index-tablespace=regress_pgbench_tap_1_ts --partitions=2 --partition-method=hash', + 0, + [qr{^$}i], + [ + qr{dropping old tables}, + qr{creating tables}, + qr{creating 2 partitions}, + qr{vacuuming}, + qr{creating primary keys}, + qr{creating foreign keys}, + qr{(?!vacuuming)}, # no vacuum + qr{done in \d+\.\d\d s } + ], + 'pgbench scale 1 initialization'); + +# Test interaction of --init-steps with legacy step-selection options +$node->pgbench( + '--initialize --init-steps=dtpvGvv --no-vacuum --foreign-keys --unlogged-tables --partitions=3', + 0, + [qr{^$}], + [ + qr{dropping old tables}, + qr{creating tables}, + qr{creating 3 partitions}, + qr{creating primary keys}, + qr{generating data \(server-side\)}, + qr{creating foreign keys}, + qr{(?!vacuuming)}, # no vacuum + qr{done in \d+\.\d\d s } + ], + 'pgbench --init-steps'); + +# Run all builtin scripts, for a few transactions each +$node->pgbench( + '--transactions=5 -Dfoo=bla --client=2 --protocol=simple --builtin=t' + . ' --connect -n -v -n', + 0, + [ + qr{builtin: TPC-B}, + qr{clients: 2\b}, + qr{processed: 10/10}, + qr{mode: simple}, + qr{maximum number of tries: 1} + ], + [qr{^$}], + 'pgbench tpcb-like'); + +$node->pgbench( + '--transactions=20 --client=5 -M extended --builtin=si -C --no-vacuum -s 1', + 0, + [ + qr{builtin: simple update}, + qr{clients: 5\b}, + qr{threads: 1\b}, + qr{processed: 100/100}, + qr{mode: extended} + ], + [qr{scale option ignored}], + 'pgbench simple update'); + +$node->pgbench( + '-t 100 -c 7 -M prepared -b se --debug', + 0, + [ + qr{builtin: select only}, + qr{clients: 7\b}, + qr{threads: 1\b}, + qr{processed: 700/700}, + qr{mode: prepared} + ], + [ + qr{vacuum}, qr{client 0}, qr{client 1}, qr{sending}, + qr{receiving}, qr{executing} + ], + 'pgbench select only'); + +# check if threads are supported +my $nthreads = 2; + +{ + my ($stderr); + run_log([ 'pgbench', '-j', '2', '--bad-option' ], '2>', \$stderr); + $nthreads = 1 if $stderr =~ m/threads are not supported on this platform/; +} + +# run custom scripts +$node->pgbench( + "-t 100 -c 1 -j $nthreads -M prepared -n", + 0, + [ + qr{type: multiple scripts}, + qr{mode: prepared}, + qr{script 1: .*/001_pgbench_custom_script_1}, + qr{weight: 2}, + qr{script 2: .*/001_pgbench_custom_script_2}, + qr{weight: 1}, + qr{processed: 100/100} + ], + [qr{^$}], + 'pgbench custom scripts', + { + '001_pgbench_custom_script_1@1' => q{-- select only +\set aid random(1, :scale * 100000) +SELECT abalance::INTEGER AS balance + FROM pgbench_accounts + WHERE aid=:aid; +}, + '001_pgbench_custom_script_2@2' => q{-- special variables +BEGIN; +\set foo 1 +-- cast are needed for typing under -M prepared +SELECT :foo::INT + :scale::INT * :client_id::INT AS bla; +COMMIT; +} + }); + +$node->pgbench( + '-n -t 10 -c 1 -M simple', + 0, + [ + qr{type: .*/001_pgbench_custom_script_3}, + qr{processed: 10/10}, + qr{mode: simple} + ], + [qr{^$}], + 'pgbench custom script', + { + '001_pgbench_custom_script_3' => q{-- select only variant +\set aid random(1, :scale * 100000) +BEGIN; +SELECT abalance::INTEGER AS balance + FROM pgbench_accounts + WHERE aid=:aid; +COMMIT; +} + }); + +$node->pgbench( + '-n -t 10 -c 2 -M extended', + 0, + [ + qr{type: .*/001_pgbench_custom_script_4}, + qr{processed: 20/20}, + qr{mode: extended} + ], + [qr{^$}], + 'pgbench custom script', + { + '001_pgbench_custom_script_4' => q{-- select only variant +\set aid random(1, :scale * 100000) +BEGIN; +SELECT abalance::INTEGER AS balance + FROM pgbench_accounts + WHERE aid=:aid; +COMMIT; +} + }); + +# Verify server logging of query parameters. +# (This doesn't really belong here, but pgbench is a convenient way +# to issue commands using extended query mode with parameters.) + +# 1. Logging neither with errors nor with statements +$node->append_conf('postgresql.conf', + "log_min_duration_statement = 0\n" + . "log_parameter_max_length = 0\n" + . "log_parameter_max_length_on_error = 0"); +$node->reload; +$node->pgbench( + '-n -t1 -c1 -M prepared', + 2, + [], + [ + qr{ERROR: invalid input syntax for type json}, + qr{(?!unnamed portal with parameters)} + ], + 'server parameter logging', + { + '001_param_1' => q[select '{ invalid ' as value \gset +select $$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$$ as long \gset +select column1::jsonb from (values (:value), (:long)) as q; +] + }); +my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile); +unlike( + $log, + qr[DETAIL: parameters: \$1 = '\{ invalid ',], + "no parameters logged"); +$log = undef; + +# 2. Logging truncated parameters on error, full with statements +$node->append_conf('postgresql.conf', + "log_parameter_max_length = -1\n" + . "log_parameter_max_length_on_error = 64"); +$node->reload; +$node->pgbench( + '-n -t1 -c1 -M prepared', + 2, + [], + [ + qr{ERROR: division by zero}, + qr{CONTEXT: unnamed portal with parameters: \$1 = '1', \$2 = NULL} + ], + 'server parameter logging', + { + '001_param_2' => q{select '1' as one \gset +SELECT 1 / (random() / 2)::int, :one::int, :two::int; +} + }); +$node->pgbench( + '-n -t1 -c1 -M prepared', + 2, + [], + [ + qr{ERROR: invalid input syntax for type json}, + qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+unnamed portal with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que \.\.\.']m + ], + 'server parameter logging', + { + '001_param_3' => q[select '{ invalid ' as value \gset +select $$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$$ as long \gset +select column1::jsonb from (values (:value), (:long)) as q; +] + }); +$log = PostgreSQL::Test::Utils::slurp_file($node->logfile); +like( + $log, + qr[DETAIL: parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia\?'''], + "parameter report does not truncate"); +$log = undef; + +# 3. Logging full parameters on error, truncated with statements +$node->append_conf('postgresql.conf', + "log_min_duration_statement = -1\n" + . "log_parameter_max_length = 7\n" + . "log_parameter_max_length_on_error = -1"); +$node->reload; +$node->pgbench( + '-n -t1 -c1 -M prepared', + 2, + [], + [ + qr{ERROR: division by zero}, + qr{CONTEXT: unnamed portal with parameters: \$1 = '1', \$2 = NULL} + ], + 'server parameter logging', + { + '001_param_4' => q{select '1' as one \gset +SELECT 1 / (random() / 2)::int, :one::int, :two::int; +} + }); + +$node->append_conf('postgresql.conf', "log_min_duration_statement = 0"); +$node->reload; +$node->pgbench( + '-n -t1 -c1 -M prepared', + 2, + [], + [ + qr{ERROR: invalid input syntax for type json}, + qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+unnamed portal with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia\?']m + ], + 'server parameter logging', + { + '001_param_5' => q[select '{ invalid ' as value \gset +select $$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$$ as long \gset +select column1::jsonb from (values (:value), (:long)) as q; +] + }); +$log = PostgreSQL::Test::Utils::slurp_file($node->logfile); +like( + $log, + qr[DETAIL: parameters: \$1 = '\{ inval\.\.\.', \$2 = '''Valame\.\.\.'], + "parameter report truncates"); +$log = undef; + +# Check that bad parameters are reported during typinput phase of BIND +$node->pgbench( + '-n -t1 -c1 -M prepared', + 2, + [], + [ + qr{ERROR: invalid input syntax for type smallint: "1a"}, + qr{CONTEXT: unnamed portal parameter \$2 = '1a'} + ], + 'server parameter logging', + { + '001_param_6' => q{select 42 as value1, '1a' as value2 \gset +select :value1::smallint, :value2::smallint; +} + }); + +# Restore default logging config +$node->append_conf('postgresql.conf', + "log_min_duration_statement = -1\n" + . "log_parameter_max_length_on_error = 0\n" + . "log_parameter_max_length = -1"); +$node->reload; + +# test expressions +$node->pgbench( + '--random-seed=5432 -t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dn=null -Dt=t -Df=of -Dd=1.0', + 0, + [ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ], + [ + qr{setting random seed to 5432\b}, + + # After explicit seeding, the four random checks (1-3,20) are + # deterministic; but see also magic values in checks 111,113. + qr{command=1.: int 17\b}, # uniform random + qr{command=2.: int 104\b}, # exponential random + qr{command=3.: int 1498\b}, # gaussian random + qr{command=4.: int 4\b}, + qr{command=5.: int 5\b}, + qr{command=6.: int 6\b}, + qr{command=7.: int 7\b}, + qr{command=8.: int 8\b}, + qr{command=9.: int 9\b}, + qr{command=10.: int 10\b}, + qr{command=11.: int 11\b}, + qr{command=12.: int 12\b}, + qr{command=15.: double 15\b}, + qr{command=16.: double 16\b}, + qr{command=17.: double 17\b}, + qr{command=20.: int 3\b}, # zipfian random + 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\+0?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}, + qr{command=49.: int -5817877081768721676\b}, + qr{command=50.: boolean true\b}, + qr{command=51.: int -7793829335365542153\b}, + qr{command=52.: int -?\d+\b}, + qr{command=53.: boolean true\b}, + qr{command=65.: int 65\b}, + qr{command=74.: int 74\b}, + qr{command=83.: int 83\b}, + qr{command=86.: int 86\b}, + qr{command=93.: int 93\b}, + qr{command=95.: int 0\b}, + qr{command=96.: int 1\b}, # :scale + qr{command=97.: int 0\b}, # :client_id + qr{command=98.: int 5432\b}, # :random_seed + qr{command=99.: int -9223372036854775808\b}, # min int + qr{command=100.: int 9223372036854775807\b}, # max int + # pseudorandom permutation tests + qr{command=101.: boolean true\b}, + qr{command=102.: boolean true\b}, + qr{command=103.: boolean true\b}, + qr{command=104.: boolean true\b}, + qr{command=105.: boolean true\b}, + qr{command=109.: boolean true\b}, + qr{command=110.: boolean true\b}, + qr{command=111.: boolean true\b}, + qr{command=113.: boolean true\b}, + ], + 'pgbench expressions', + { + '001_pgbench_expressions' => q{-- integer functions +\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 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 % (((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 * Ln(Exp(1.0))) +\set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3) +-- reset variables +\set i1 0 +\set d1 false +-- yet another integer function +\set id debug(random_zipfian(1, 9, 1.3)) +--- pow and power +\set poweri debug(pow(-3,3)) +\set powerd debug(pow(2.0,10)) +\set poweriz debug(pow(0,0)) +\set powerdz debug(pow(0.0,0.0)) +\set powernegi debug(pow(-2,-3)) +\set powernegd debug(pow(-2.0,-3.0)) +\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) +-- hash functions +\set h0 debug(hash(10, 5432)) +\set h1 debug(:h0 = hash_murmur2(10, 5432)) +\set h3 debug(hash_fnv1a(10, 5432)) +\set h4 debug(hash(10)) +\set h5 debug(hash(10) = hash(10, :default_seed)) +-- 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; +-- if tests +\set nope 0 +\if 1 > 0 +\set id debug(65) +\elif 0 +\set nope 1 +\else +\set nope 1 +\endif +\if 1 < 0 +\set nope 1 +\elif 1 > 0 +\set ie debug(74) +\else +\set nope 1 +\endif +\if 1 < 0 +\set nope 1 +\elif 1 < 0 +\set nope 1 +\else +\set if debug(83) +\endif +\if 1 = 1 +\set ig debug(86) +\elif 0 +\set nope 1 +\endif +\if 1 = 0 +\set nope 1 +\elif 1 <> 0 +\set ih debug(93) +\endif +-- must be zero if false branches where skipped +\set nope debug(:nope) +-- check automatic variables +\set sc debug(:scale) +\set ci debug(:client_id) +\set rs debug(:random_seed) +-- minint constant parsing +\set min debug(-9223372036854775808) +\set max debug(-(:min + 1)) +-- parametric pseudorandom permutation function +\set t debug(permute(0, 2) + permute(1, 2) = 1) +\set t debug(permute(0, 3) + permute(1, 3) + permute(2, 3) = 3) +\set t debug(permute(0, 4) + permute(1, 4) + permute(2, 4) + permute(3, 4) = 6) +\set t debug(permute(0, 5) + permute(1, 5) + permute(2, 5) + permute(3, 5) + permute(4, 5) = 10) +\set t debug(permute(0, 16) + permute(1, 16) + permute(2, 16) + permute(3, 16) + \ + permute(4, 16) + permute(5, 16) + permute(6, 16) + permute(7, 16) + \ + permute(8, 16) + permute(9, 16) + permute(10, 16) + permute(11, 16) + \ + permute(12, 16) + permute(13, 16) + permute(14, 16) + permute(15, 16) = 120) +-- random sanity checks +\set size random(2, 1000) +\set v random(0, :size - 1) +\set p permute(:v, :size) +\set t debug(0 <= :p and :p < :size and :p = permute(:v + :size, :size) and :p <> permute(:v + 1, :size)) +-- actual values +\set t debug(permute(:v, 1) = 0) +\set t debug(permute(0, 2, 5431) = 0 and permute(1, 2, 5431) = 1 and \ + permute(0, 2, 5433) = 1 and permute(1, 2, 5433) = 0) +-- check permute's portability across architectures +\set size debug(:max - 10) +\set t debug(permute(:size-1, :size, 5432) = 520382784483822430 and \ + permute(:size-2, :size, 5432) = 1143715004660802862 and \ + permute(:size-3, :size, 5432) = 447293596416496998 and \ + permute(:size-4, :size, 5432) = 916527772266572956 and \ + permute(:size-5, :size, 5432) = 2763809008686028849 and \ + permute(:size-6, :size, 5432) = 8648551549198294572 and \ + permute(:size-7, :size, 5432) = 4542876852200565125) +} + }); + +# random determinism when seeded +$node->safe_psql('postgres', + 'CREATE UNLOGGED TABLE seeded_random(seed INT8 NOT NULL, rand TEXT NOT NULL, val INTEGER NOT NULL);' +); + +# same value to check for determinism +my $seed = int(rand(1000000000)); +for my $i (1, 2) +{ + $node->pgbench( + "--random-seed=$seed -t 1", + 0, + [qr{processed: 1/1}], + [qr{setting random seed to $seed\b}], + "random seeded with $seed", + { + "001_pgbench_random_seed_$i" => q{-- test random functions +\set ur random(1000, 1999) +\set er random_exponential(2000, 2999, 2.0) +\set gr random_gaussian(3000, 3999, 3.0) +\set zr random_zipfian(4000, 4999, 1.5) +INSERT INTO seeded_random(seed, rand, val) VALUES + (:random_seed, 'uniform', :ur), + (:random_seed, 'exponential', :er), + (:random_seed, 'gaussian', :gr), + (:random_seed, 'zipfian', :zr); +} + }); +} + +# check that all runs generated the same 4 values +my ($ret, $out, $err) = $node->psql('postgres', + 'SELECT seed, rand, val, COUNT(*) FROM seeded_random GROUP BY seed, rand, val' +); + +ok($ret == 0, "psql seeded_random count ok"); +ok($err eq '', "psql seeded_random count stderr is empty"); +ok($out =~ /\b$seed\|uniform\|1\d\d\d\|2/, + "psql seeded_random count uniform"); +ok( $out =~ /\b$seed\|exponential\|2\d\d\d\|2/, + "psql seeded_random count exponential"); +ok( $out =~ /\b$seed\|gaussian\|3\d\d\d\|2/, + "psql seeded_random count gaussian"); +ok($out =~ /\b$seed\|zipfian\|4\d\d\d\|2/, + "psql seeded_random count zipfian"); + +$node->safe_psql('postgres', 'DROP TABLE seeded_random;'); + +# backslash commands +$node->pgbench( + '-t 1', 0, + [ + qr{type: .*/001_pgbench_backslash_commands}, + qr{processed: 1/1}, + qr{shell-echo-output} + ], + [qr{command=8.: int 1\b}], + 'pgbench backslash commands', + { + '001_pgbench_backslash_commands' => q{-- run set +\set zero 0 +\set one 1.0 +-- sleep +\sleep :one ms +\sleep 100 us +\sleep 0 s +\sleep :zero +-- setshell and continuation +\setshell another_one\ + echo \ + :one +\set n debug(:another_one) +-- shell +\shell echo shell-echo-output +} + }); + +# working \gset +$node->pgbench( + '-t 1', 0, + [ qr{type: .*/001_pgbench_gset}, qr{processed: 1/1} ], + [ + qr{command=3.: int 0\b}, + qr{command=5.: int 1\b}, + qr{command=6.: int 2\b}, + qr{command=8.: int 3\b}, + qr{command=10.: int 4\b}, + qr{command=12.: int 5\b} + ], + 'pgbench gset command', + { + '001_pgbench_gset' => q{-- test gset +-- no columns +SELECT \gset +-- one value +SELECT 0 AS i0 \gset +\set i debug(:i0) +-- two values +SELECT 1 AS i1, 2 AS i2 \gset +\set i debug(:i1) +\set i debug(:i2) +-- with prefix +SELECT 3 AS i3 \gset x_ +\set i debug(:x_i3) +-- overwrite existing variable +SELECT 0 AS i4, 4 AS i4 \gset +\set i debug(:i4) +-- work on the last SQL command under \; +\; \; SELECT 0 AS i5 \; SELECT 5 AS i5 \; \; \gset +\set i debug(:i5) +} + }); +# \gset cannot accept more than one row, causing command to fail. +$node->pgbench( + '-t 1', 2, + [ qr{type: .*/001_pgbench_gset_two_rows}, qr{processed: 0/1} ], + [qr{expected one row, got 2\b}], + 'pgbench gset command with two rows', + { + '001_pgbench_gset_two_rows' => q{ +SELECT 5432 AS fail UNION SELECT 5433 ORDER BY 1 \gset +} + }); + +# working \aset +# Valid cases. +$node->pgbench( + '-t 1', 0, + [ qr{type: .*/001_pgbench_aset}, qr{processed: 1/1} ], + [ qr{command=3.: int 8\b}, qr{command=4.: int 7\b} ], + 'pgbench aset command', + { + '001_pgbench_aset' => q{ +-- test aset, which applies to a combined query +\; SELECT 6 AS i6 \; SELECT 7 AS i7 \; \aset +-- unless it returns more than one row, last is kept +SELECT 8 AS i6 UNION SELECT 9 ORDER BY 1 DESC \aset +\set i debug(:i6) +\set i debug(:i7) +} + }); +# Empty result set with \aset, causing command to fail. +$node->pgbench( + '-t 1', 2, + [ qr{type: .*/001_pgbench_aset_empty}, qr{processed: 0/1} ], + [ + qr{undefined variable \"i8\"}, + qr{evaluation of meta-command failed\b} + ], + 'pgbench aset command with empty result', + { + '001_pgbench_aset_empty' => q{ +-- empty result +\; SELECT 5432 AS i8 WHERE FALSE \; \aset +\set i debug(:i8) +} + }); + +# Working \startpipeline +$node->pgbench( + '-t 1 -n -M extended', + 0, + [ qr{type: .*/001_pgbench_pipeline}, qr{actually processed: 1/1} ], + [], + 'working \startpipeline', + { + '001_pgbench_pipeline' => q{ +-- test startpipeline +\startpipeline +} . "select 1;\n" x 10 . q{ +\endpipeline +} + }); + +# Working \startpipeline in prepared query mode +$node->pgbench( + '-t 1 -n -M prepared', + 0, + [ qr{type: .*/001_pgbench_pipeline_prep}, qr{actually processed: 1/1} ], + [], + 'working \startpipeline', + { + '001_pgbench_pipeline_prep' => q{ +-- test startpipeline +\startpipeline +\endpipeline +\startpipeline +} . "select 1;\n" x 10 . q{ +\endpipeline +} + }); + +# Try \startpipeline twice +$node->pgbench( + '-t 1 -n -M extended', + 2, + [], + [qr{already in pipeline mode}], + 'error: call \startpipeline twice', + { + '001_pgbench_pipeline_2' => q{ +-- startpipeline twice +\startpipeline +\startpipeline +} + }); + +# Try to end a pipeline that hasn't started +$node->pgbench( + '-t 1 -n -M extended', + 2, + [], + [qr{not in pipeline mode}], + 'error: \endpipeline with no start', + { + '001_pgbench_pipeline_3' => q{ +-- pipeline not started +\endpipeline +} + }); + +# Try \gset in pipeline mode +$node->pgbench( + '-t 1 -n -M extended', + 2, + [], + [qr{gset is not allowed in pipeline mode}], + 'error: \gset not allowed in pipeline mode', + { + '001_pgbench_pipeline_4' => q{ +\startpipeline +select 1 \gset f +\endpipeline +} + }); + +# Working \startpipeline in prepared query mode with serializable +$node->pgbench( + '-c4 -j2 -t 10 -n -M prepared', + 0, + [ + qr{type: .*/001_pgbench_pipeline_serializable}, + qr{actually processed: (\d+)/\1} + ], + [], + 'working \startpipeline with serializable', + { + '001_pgbench_pipeline_serializable' => q{ +-- test startpipeline with serializable +\startpipeline +BEGIN ISOLATION LEVEL SERIALIZABLE; +} . "select 1;\n" x 10 . q{ +END; +\endpipeline +} + }); + +# trigger many expression errors +my @errors = ( + + # [ test name, expected status, expected stderr, script ] + # SQL + [ + 'sql syntax error', + 2, + [ + qr{ERROR: syntax error}, + qr{prepared statement .* does not exist} + ], + q{-- SQL syntax error + SELECT 1 + ; +} + ], + [ + 'sql too many args', 1, + [qr{statement has too many arguments.*\b255\b}], + q{-- MAX_ARGS=256 for prepared +\set i 0 +SELECT LEAST(} . join(', ', (':i') x 256) . q{)} + ], + + # SHELL + [ + 'shell bad command', 2, + [qr{\(shell\) .* meta-command failed}], q{\shell no-such-command} + ], + [ + 'shell undefined variable', 2, + [qr{undefined variable ":nosuchvariable"}], + q{-- undefined variable in shell +\shell echo ::foo :nosuchvariable +} + ], + [ 'shell missing command', 1, [qr{missing command }], q{\shell} ], + [ + 'shell too many args', 1, [qr{too many arguments in command "shell"}], + q{-- 256 arguments to \shell +\shell echo } . join(' ', ('arg') x 255) + ], + + # SET + [ + 'set syntax error', 1, + [qr{syntax error in command "set"}], q{\set i 1 +} + ], + [ + 'set no such function', 1, + [qr{unexpected function name}], q{\set i noSuchFunction()} + ], + [ + 'set invalid variable name', 2, + [qr{invalid variable name}], q{\set . 1} + ], + [ 'set division by zero', 2, [qr{division by zero}], q{\set i 1/0} ], + [ + 'set undefined variable', + 2, + [qr{undefined variable "nosuchvariable"}], + q{\set i :nosuchvariable} + ], + [ 'set unexpected char', 1, [qr{unexpected character .;.}], q{\set i ;} ], + [ + 'set too many args', + 2, + [qr{too many function arguments}], + q{\set i least(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)} + ], + [ + 'set empty random range', 2, + [qr{empty range given to random}], q{\set i random(5,3)} + ], + [ + 'set random range too large', 2, + [qr{random range is too large}], q{\set i random(:minint, :maxint)} + ], + [ + 'set gaussian param too small', + 2, + [qr{gaussian param.* at least 2}], + q{\set i random_gaussian(0, 10, 1.0)} + ], + [ + 'set exponential param greater 0', + 2, + [qr{exponential parameter must be greater }], + q{\set i random_exponential(0, 10, 0.0)} + ], + [ + 'set zipfian param to 1', + 2, + [qr{zipfian parameter must be in range \[1\.001, 1000\]}], + q{\set i random_zipfian(0, 10, 1)} + ], + [ + 'set zipfian param too large', + 2, + [qr{zipfian parameter must be in range \[1\.001, 1000\]}], + q{\set i random_zipfian(0, 10, 1000000)} + ], + [ + 'set non numeric value', 2, + [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 not a bool', 2, + [qr{cannot coerce double to boolean}], q{\set b NOT 0.0} + ], + [ + 'set not an int', 2, + [qr{cannot coerce boolean to int}], q{\set i TRUE + 2} + ], + [ + 'set not a double', 2, + [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', 2, + [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())} + ], + + # SET: ARITHMETIC OVERFLOW DETECTION + [ + 'set double to int overflow', 2, + [qr{double to int overflow for 100}], q{\set i int(1E32)} + ], + [ + 'set bigint add overflow', 2, + [qr{int add out}], q{\set i (1<<62) + (1<<62)} + ], + [ + 'set bigint sub overflow', + 2, [qr{int sub out}], q{\set i 0 - (1<<62) - (1<<62) - (1<<62)} + ], + [ + 'set bigint mul overflow', 2, + [qr{int mul out}], q{\set i 2 * (1<<62)} + ], + [ + 'set bigint div out of range', 2, + [qr{bigint div out of range}], q{\set i :minint / -1} + ], + + # SETSHELL + [ + 'setshell not an int', 2, + [qr{command must return an integer}], q{\setshell i echo -n one} + ], + [ 'setshell missing arg', 1, [qr{missing argument }], q{\setshell var} ], + [ + 'setshell no such command', 2, + [qr{could not read result }], q{\setshell var no-such-command} + ], + + # SLEEP + [ + 'sleep undefined variable', 2, + [qr{sleep: undefined variable}], q{\sleep :nosuchvariable} + ], + [ + 'sleep too many args', 1, + [qr{too many arguments}], q{\sleep too many args} + ], + [ + 'sleep missing arg', 1, + [ qr{missing argument}, qr{\\sleep} ], q{\sleep} + ], + [ + 'sleep unknown unit', 1, + [qr{unrecognized time unit}], q{\sleep 1 week} + ], + + # MISC + [ + 'misc invalid backslash command', 1, + [qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand} + ], + [ 'misc empty script', 1, [qr{empty command list for script}], q{} ], + [ + 'bad boolean', 2, + [qr{malformed variable.*trueXXX}], q{\set b :badtrue or true} + ], + [ + 'invalid permute size', + 2, + [qr{permute size parameter must be greater than zero}], + q{\set i permute(0, 0)} + ], + + # GSET + [ + 'gset no row', 2, + [qr{expected one row, got 0\b}], q{SELECT WHERE FALSE \gset} + ], + [ 'gset alone', 1, [qr{gset must follow an SQL command}], q{\gset} ], + [ + 'gset no SQL', 1, + [qr{gset must follow an SQL command}], q{\set i +1 +\gset} + ], + [ + 'gset too many arguments', 1, + [qr{too many arguments}], q{SELECT 1 \gset a b} + ], + [ + 'gset after gset', 1, + [qr{gset must follow an SQL command}], q{SELECT 1 AS i \gset +\gset} + ], + [ + 'gset non SELECT', + 2, + [qr{expected one row, got 0}], + q{DROP TABLE IF EXISTS no_such_table \gset} + ], + [ + 'gset bad default name', 2, + [qr{error storing into variable \?column\?}], q{SELECT 1 \gset} + ], + [ + 'gset bad name', + 2, + [qr{error storing into variable bad name!}], + q{SELECT 1 AS "bad name!" \gset} + ],); + +for my $e (@errors) +{ + my ($name, $status, $re, $script, $no_prepare) = @$e; + $status != 0 or die "invalid expected status for test \"$name\""; + my $n = '001_pgbench_error_' . $name; + $n =~ s/ /_/g; + $node->pgbench( + '-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX' + . ' -Dmaxint=9223372036854775807 -Dminint=-9223372036854775808' + . ($no_prepare ? '' : ' -M prepared'), + $status, + [ $status == 1 ? qr{^$} : qr{processed: 0/1} ], + $re, + 'pgbench script error: ' . $name, + { $n => $script }); +} + +# throttling +$node->pgbench( + '-t 100 -S --rate=100000 --latency-limit=1000000 -c 2 -n -r', + 0, + [ qr{processed: 200/200}, qr{builtin: select only} ], + [qr{^$}], + 'pgbench throttling'); + +$node->pgbench( + + # given the expected rate and the 2 ms tx duration, at most one is executed + '-t 10 --rate=100000 --latency-limit=1 -n -r', + 0, + [ + qr{processed: [01]/10}, + qr{type: .*/001_pgbench_sleep}, + qr{above the 1.0 ms latency limit: [01]/} + ], + [qr{^$}], + 'pgbench late throttling', + { '001_pgbench_sleep' => q{\sleep 2ms} }); + +# return a list of files from directory $dir matching regexpr $re +# this works around glob portability and escaping issues +sub list_files +{ + my ($dir, $re) = @_; + opendir my $dh, $dir or die "cannot opendir $dir: $!"; + my @files = grep /$re/, readdir $dh; + closedir $dh or die "cannot closedir $dir: $!"; + return map { $dir . '/' . $_ } @files; +} + +# Check log contents and clean them up: +# $dir: directory holding logs +# $prefix: file prefix for per-thread logs +# $nb: number of expected files +# $min/$max: minimum and maximum number of lines in log files +# $re: regular expression each log line has to match +sub check_pgbench_logs +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($dir, $prefix, $nb, $min, $max, $re) = @_; + + # $prefix is simple enough, thus does not need escaping + my @logs = list_files($dir, qr{^$prefix\..*$}); + ok(@logs == $nb, "number of log files"); + ok(grep(/\/$prefix\.\d+(\.\d+)?$/, @logs) == $nb, "file name format"); + + my $log_number = 0; + for my $log (sort @logs) + { + # Check the contents of each log file. + my $contents_raw = slurp_file($log); + + my @contents = split(/\n/, $contents_raw); + my $clen = @contents; + ok( $min <= $clen && $clen <= $max, + "transaction count for $log ($clen)"); + my $clen_match = grep(/$re/, @contents); + ok($clen_match == $clen, "transaction format for $prefix"); + + # Show more information if some logs don't match + # to help with debugging. + if ($clen_match != $clen) + { + foreach my $log (@contents) + { + print "# Log entry not matching: $log\n" + unless $log =~ /$re/; + } + } + } + return; +} + +my $bdir = $node->basedir; + +# Run with sampling rate, 2 clients with 50 transactions each. +$node->pgbench( + "-n -S -t 50 -c 2 --log --sampling-rate=0.5", 0, + [ qr{select only}, qr{processed: 100/100} ], [qr{^$}], + 'pgbench logs', undef, + "--log-prefix=$bdir/001_pgbench_log_2"); +# The IDs of the clients (1st field) in the logs should be either 0 or 1. +check_pgbench_logs($bdir, '001_pgbench_log_2', 1, 8, 92, + qr{^[01] \d{1,2} \d+ \d \d+ \d+$}); + +# Run with different read-only option pattern, 1 client with 10 transactions. +$node->pgbench( + "-n -b select-only -t 10 -l", 0, + [ qr{select only}, qr{processed: 10/10} ], [qr{^$}], + 'pgbench logs contents', undef, + "--log-prefix=$bdir/001_pgbench_log_3"); +# The ID of a single client (1st field) should match 0. +check_pgbench_logs($bdir, '001_pgbench_log_3', 1, 10, 10, + qr{^0 \d{1,2} \d+ \d \d+ \d+$}); + +# abortion of the client if the script contains an incomplete transaction block +$node->pgbench( + '--no-vacuum', + 2, + [qr{processed: 1/10}], + [ + qr{client 0 aborted: end of script reached without completing the last transaction} + ], + 'incomplete transaction block', + { '001_pgbench_incomplete_transaction_block' => q{BEGIN;SELECT 1;} }); + +# Test the concurrent update in the table row and deadlocks. + +$node->safe_psql('postgres', + 'CREATE UNLOGGED TABLE first_client_table (value integer); ' + . 'CREATE UNLOGGED TABLE xy (x integer, y integer); ' + . 'INSERT INTO xy VALUES (1, 2);'); + +# Serialization error and retry + +local $ENV{PGOPTIONS} = "-c default_transaction_isolation=repeatable\\ read"; + +# Check that we have a serialization error and the same random value of the +# delta variable in the next try +my $err_pattern = + "(client (0|1) sending UPDATE xy SET y = y \\+ -?\\d+\\b).*" + . "client \\2 got an error in command 3 \\(SQL\\) of script 0; " + . "ERROR: could not serialize access due to concurrent update\\b.*" + . "\\1"; + +$node->pgbench( + "-n -c 2 -t 1 -d --verbose-errors --max-tries 2", + 0, + [ + qr{processed: 2/2\b}, + qr{number of transactions retried: 1\b}, + qr{total number of retries: 1\b} + ], + [qr/$err_pattern/s], + 'concurrent update with retrying', + { + '001_pgbench_serialization' => q{ +-- What's happening: +-- The first client starts the transaction with the isolation level Repeatable +-- Read: +-- +-- BEGIN; +-- UPDATE xy SET y = ... WHERE x = 1; +-- +-- The second client starts a similar transaction with the same isolation level: +-- +-- BEGIN; +-- UPDATE xy SET y = ... WHERE x = 1; +-- <waiting for the first client> +-- +-- The first client commits its transaction, and the second client gets a +-- serialization error. + +\set delta random(-5000, 5000) + +-- The second client will stop here +SELECT pg_advisory_lock(0); + +-- Start transaction with concurrent update +BEGIN; +UPDATE xy SET y = y + :delta WHERE x = 1 AND pg_advisory_lock(1) IS NOT NULL; + +-- Wait for the second client +DO $$ +DECLARE + exists boolean; + waiters integer; +BEGIN + -- The second client always comes in second, and the number of rows in the + -- table first_client_table reflect this. Here the first client inserts a row, + -- so the second client will see a non-empty table when repeating the + -- transaction after the serialization error. + SELECT EXISTS (SELECT * FROM first_client_table) INTO STRICT exists; + IF NOT exists THEN + -- Let the second client begin + PERFORM pg_advisory_unlock(0); + -- And wait until the second client tries to get the same lock + LOOP + SELECT COUNT(*) INTO STRICT waiters FROM pg_locks WHERE + locktype = 'advisory' AND objsubid = 1 AND + ((classid::bigint << 32) | objid::bigint = 1::bigint) AND NOT granted; + IF waiters = 1 THEN + INSERT INTO first_client_table VALUES (1); + + -- Exit loop + EXIT; + END IF; + END LOOP; + END IF; +END$$; + +COMMIT; +SELECT pg_advisory_unlock_all(); +} + }); + +# Clean up + +$node->safe_psql('postgres', 'DELETE FROM first_client_table;'); + +local $ENV{PGOPTIONS} = "-c default_transaction_isolation=read\\ committed"; + +# Deadlock error and retry + +# Check that we have a deadlock error +$err_pattern = + "client (0|1) got an error in command (3|5) \\(SQL\\) of script 0; " + . "ERROR: deadlock detected\\b"; + +$node->pgbench( + "-n -c 2 -t 1 --max-tries 2 --verbose-errors", + 0, + [ + qr{processed: 2/2\b}, + qr{number of transactions retried: 1\b}, + qr{total number of retries: 1\b} + ], + [qr{$err_pattern}], + 'deadlock with retrying', + { + '001_pgbench_deadlock' => q{ +-- What's happening: +-- The first client gets the lock 2. +-- The second client gets the lock 3 and tries to get the lock 2. +-- The first client tries to get the lock 3 and one of them gets a deadlock +-- error. +-- +-- A client that does not get a deadlock error must hold a lock at the +-- transaction start. Thus in the end it releases all of its locks before the +-- client with the deadlock error starts a retry (we do not want any errors +-- again). + +-- Since the client with the deadlock error has not released the blocking locks, +-- let's do this here. +SELECT pg_advisory_unlock_all(); + +-- The second client and the client with the deadlock error stop here +SELECT pg_advisory_lock(0); +SELECT pg_advisory_lock(1); + +-- The second client and the client with the deadlock error always come after +-- the first and the number of rows in the table first_client_table reflects +-- this. Here the first client inserts a row, so in the future the table is +-- always non-empty. +DO $$ +DECLARE + exists boolean; +BEGIN + SELECT EXISTS (SELECT * FROM first_client_table) INTO STRICT exists; + IF exists THEN + -- We are the second client or the client with the deadlock error + + -- The first client will take care by itself of this lock (see below) + PERFORM pg_advisory_unlock(0); + + PERFORM pg_advisory_lock(3); + + -- The second client can get a deadlock here + PERFORM pg_advisory_lock(2); + ELSE + -- We are the first client + + -- This code should not be used in a new transaction after an error + INSERT INTO first_client_table VALUES (1); + + PERFORM pg_advisory_lock(2); + END IF; +END$$; + +DO $$ +DECLARE + num_rows integer; + waiters integer; +BEGIN + -- Check if we are the first client + SELECT COUNT(*) FROM first_client_table INTO STRICT num_rows; + IF num_rows = 1 THEN + -- This code should not be used in a new transaction after an error + INSERT INTO first_client_table VALUES (2); + + -- Let the second client begin + PERFORM pg_advisory_unlock(0); + PERFORM pg_advisory_unlock(1); + + -- Make sure the second client is ready for deadlock + LOOP + SELECT COUNT(*) INTO STRICT waiters FROM pg_locks WHERE + locktype = 'advisory' AND + objsubid = 1 AND + ((classid::bigint << 32) | objid::bigint = 2::bigint) AND + NOT granted; + + IF waiters = 1 THEN + -- Exit loop + EXIT; + END IF; + END LOOP; + + PERFORM pg_advisory_lock(0); + -- And the second client took care by itself of the lock 1 + END IF; +END$$; + +-- The first client can get a deadlock here +SELECT pg_advisory_lock(3); + +SELECT pg_advisory_unlock_all(); +} + }); + +# Clean up +$node->safe_psql('postgres', 'DROP TABLE first_client_table, xy;'); + + +# done +$node->safe_psql('postgres', 'DROP TABLESPACE regress_pgbench_tap_1_ts'); +$node->stop; +done_testing(); diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl new file mode 100644 index 0000000..50bde7d --- /dev/null +++ b/src/bin/pgbench/t/002_pgbench_no_server.pl @@ -0,0 +1,387 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +# +# pgbench tests which do not need a server +# + +use strict; +use warnings; + +use PostgreSQL::Test::Utils; +use Test::More; + +# create a directory for scripts +my $testname = $0; +$testname =~ s,.*/,,; +$testname =~ s/\.pl$//; + +my $testdir = "$PostgreSQL::Test::Utils::tmp_check/t_${testname}_stuff"; +mkdir $testdir + or BAIL_OUT("could not create test directory \"${testdir}\": $!"); + +# invoke pgbench +sub pgbench +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($opts, $stat, $out, $err, $name) = @_; + command_checks_all([ 'pgbench', split(/\s+/, $opts) ], + $stat, $out, $err, $name); + return; +} + +# invoke pgbench with scripts +sub pgbench_scripts +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($opts, $stat, $out, $err, $name, $files) = @_; + my @cmd = ('pgbench', split /\s+/, $opts); + my @filenames = (); + if (defined $files) + { + for my $fn (sort keys %$files) + { + my $filename = $testdir . '/' . $fn; + + # cleanup file weight if any + $filename =~ s/\@\d+$//; + + # cleanup from prior runs + unlink $filename; + append_to_file($filename, $$files{$fn}); + push @cmd, '-f', $filename; + } + } + command_checks_all(\@cmd, $stat, $out, $err, $name); + return; +} + +# +# Option various errors +# + +my @options = ( + + # name, options, stderr checks + [ + 'bad option', + '-h home -p 5432 -U calvin -d --bad-option', + [qr{--help.*more information}] + ], + [ + 'no file', + '-f no-such-file', + [qr{could not open file "no-such-file":}] + ], + [ + 'no builtin', + '-b no-such-builtin', + [qr{no builtin script .* "no-such-builtin"}] + ], + [ + 'invalid weight', + '--builtin=select-only@one', + [qr{invalid weight specification: \@one}] + ], + [ + 'invalid weight', + '-b select-only@-1', + [qr{weight spec.* out of range .*: -1}] + ], + [ 'too many scripts', '-S ' x 129, [qr{at most 128 SQL scripts}] ], + [ + 'bad #clients', '-c three', + [qr{invalid value "three" for option -c/--clients}] + ], + [ + 'bad #threads', '-j eleven', + [qr{invalid value "eleven" for option -j/--jobs}] + ], + [ + 'bad scale', '-i -s two', + [qr{invalid value "two" for option -s/--scale}] + ], + [ + 'invalid #transactions', + '-t zil', [qr{invalid value "zil" for option -t/--transactions}] + ], + [ + 'invalid duration', + '-T ten', [qr{invalid value "ten" for option -T/--time}] + ], + [ + '-t XOR -T', + '-N -l --aggregate-interval=5 --log-prefix=notused -t 1000 -T 1', + [qr{specify either }] + ], + [ + '-T XOR -t', + '-P 1 --progress-timestamp -l --sampling-rate=0.001 -T 10 -t 1000', + [qr{specify either }] + ], + [ 'bad variable', '--define foobla', [qr{invalid variable definition}] ], + [ 'invalid fillfactor', '-F 1', [qr{-F/--fillfactor must be in range}] ], + [ 'invalid query mode', '-M no-such-mode', [qr{invalid query mode}] ], + [ + 'invalid progress', '--progress=0', + [qr{-P/--progress must be in range}] + ], + [ 'invalid rate', '--rate=0.0', [qr{invalid rate limit}] ], + [ 'invalid latency', '--latency-limit=0.0', [qr{invalid latency limit}] ], + [ + 'invalid sampling rate', '--sampling-rate=0', + [qr{invalid sampling rate}] + ], + [ + 'invalid aggregate interval', + '--aggregate-interval=-3', + [qr{--aggregate-interval must be in range}] + ], + [ + 'weight zero', + '-b se@0 -b si@0 -b tpcb@0', + [qr{weight must not be zero}] + ], + [ 'init vs run', '-i -S', [qr{cannot be used in initialization}] ], + [ 'run vs init', '-S -F 90', [qr{cannot be used in benchmarking}] ], + [ 'ambiguous builtin', '-b s', [qr{ambiguous}] ], + [ + '--progress-timestamp => --progress', '--progress-timestamp', + [qr{allowed only under}] + ], + [ + '-I without init option', + '-I dtg', + [qr{cannot be used in benchmarking mode}] + ], + [ + 'invalid init step', + '-i -I dta', + [ + qr{unrecognized initialization step}, + qr{Allowed step characters are} + ] + ], + [ + 'bad random seed', + '--random-seed=one', + [ + qr{unrecognized random seed option "one"}, + qr{Expecting an unsigned integer, "time" or "rand"}, + qr{error while setting random seed from --random-seed option} + ] + ], + [ + 'bad partition method', + '-i --partition-method=BAD', + [ qr{"range"}, qr{"hash"}, qr{"BAD"} ] + ], + [ + 'bad partition number', + '-i --partitions -1', + [qr{--partitions must be in range}] + ], + [ + 'partition method without partitioning', + '-i --partition-method=hash', + [qr{partition-method requires greater than zero --partitions}] + ], + [ + 'bad maximum number of tries', + '--max-tries -10', + [qr{invalid number of maximum tries: "-10"}] + ], + [ + 'an infinite number of tries', + '--max-tries 0', + [ + qr{an unlimited number of transaction tries can only be used with --latency-limit or a duration} + ] + ], + + # logging sub-options + [ + 'sampling => log', '--sampling-rate=0.01', + [qr{log sampling .* only when}] + ], + [ + 'sampling XOR aggregate', + '-l --sampling-rate=0.1 --aggregate-interval=3', + [qr{sampling .* aggregation .* cannot be used at the same time}] + ], + [ + 'aggregate => log', '--aggregate-interval=3', + [qr{aggregation .* only when}] + ], + [ 'log-prefix => log', '--log-prefix=x', [qr{prefix .* only when}] ], + [ + 'duration & aggregation', + '-l -T 1 --aggregate-interval=3', + [qr{aggr.* not be higher}] + ], + [ + 'duration % aggregation', + '-l -T 5 --aggregate-interval=3', + [qr{multiple}] + ],); + +for my $o (@options) +{ + my ($name, $opts, $err_checks) = @$o; + pgbench($opts, 1, [qr{^$}], $err_checks, + 'pgbench option error: ' . $name); +} + +# Help +pgbench( + '--help', 0, + [ + qr{benchmarking tool for PostgreSQL}, + qr{Usage}, + qr{Initialization options:}, + qr{Common options:}, + qr{Report bugs to} + ], + [qr{^$}], + 'pgbench help'); + +# Version +pgbench('-V', 0, [qr{^pgbench .PostgreSQL. }], [qr{^$}], 'pgbench version'); + +# list of builtins +pgbench( + '-b list', + 0, + [qr{^$}], + [ + qr{Available builtin scripts:}, qr{tpcb-like}, + qr{simple-update}, qr{select-only} + ], + 'pgbench builtin list'); + +# builtin listing +pgbench( + '--show-script se', + 0, + [qr{^$}], + [ + qr{select-only: }, qr{SELECT abalance FROM pgbench_accounts WHERE}, + qr{(?!UPDATE)}, qr{(?!INSERT)} + ], + 'pgbench builtin listing'); + +my @script_tests = ( + + # name, err, { file => contents } + [ + 'missing endif', + [qr{\\if without matching \\endif}], + { 'if-noendif.sql' => '\if 1' } + ], + [ + 'missing if on elif', + [qr{\\elif without matching \\if}], + { 'elif-noif.sql' => '\elif 1' } + ], + [ + 'missing if on else', + [qr{\\else without matching \\if}], + { 'else-noif.sql' => '\else' } + ], + [ + 'missing if on endif', + [qr{\\endif without matching \\if}], + { 'endif-noif.sql' => '\endif' } + ], + [ + 'elif after else', + [qr{\\elif after \\else}], + { 'else-elif.sql' => "\\if 1\n\\else\n\\elif 0\n\\endif" } + ], + [ + 'else after else', + [qr{\\else after \\else}], + { 'else-else.sql' => "\\if 1\n\\else\n\\else\n\\endif" } + ], + [ + 'if syntax error', + [qr{syntax error in command "if"}], + { 'if-bad.sql' => "\\if\n\\endif\n" } + ], + [ + 'elif syntax error', + [qr{syntax error in command "elif"}], + { 'elif-bad.sql' => "\\if 0\n\\elif +\n\\endif\n" } + ], + [ + 'else syntax error', + [qr{unexpected argument in command "else"}], + { 'else-bad.sql' => "\\if 0\n\\else BAD\n\\endif\n" } + ], + [ + 'endif syntax error', + [qr{unexpected argument in command "endif"}], + { 'endif-bad.sql' => "\\if 0\n\\endif BAD\n" } + ], + [ + 'not enough arguments for least', + [qr{at least one argument expected \(least\)}], + { 'bad-least.sql' => "\\set i least()\n" } + ], + [ + 'not enough arguments for greatest', + [qr{at least one argument expected \(greatest\)}], + { 'bad-greatest.sql' => "\\set i greatest()\n" } + ], + [ + 'not enough arguments for hash', + [qr{unexpected number of arguments \(hash\)}], + { 'bad-hash-1.sql' => "\\set i hash()\n" } + ], + [ + 'too many arguments for hash', + [qr{unexpected number of arguments \(hash\)}], + { 'bad-hash-2.sql' => "\\set i hash(1,2,3)\n" } + ], + # overflow + [ + 'bigint overflow 1', + [qr{bigint constant overflow}], + { 'overflow-1.sql' => "\\set i 100000000000000000000\n" } + ], + [ + 'double overflow 2', + [qr{double constant overflow}], + { 'overflow-2.sql' => "\\set d 1.0E309\n" } + ], + [ + 'double overflow 3', + [qr{double constant overflow}], + { 'overflow-3.sql' => "\\set d .1E310\n" } + ], + [ + 'set i', + [ qr{set i 1 }, qr{\^ error found here} ], + { 'set_i_op' => "\\set i 1 +\n" } + ], + [ + 'not enough arguments to permute', + [qr{unexpected number of arguments \(permute\)}], + { 'bad-permute-1.sql' => "\\set i permute(1)\n" } + ], + [ + 'too many arguments to permute', + [qr{unexpected number of arguments \(permute\)}], + { 'bad-permute-2.sql' => "\\set i permute(1, 2, 3, 4)\n" } + ],); + +for my $t (@script_tests) +{ + my ($name, $err, $files) = @$t; + pgbench_scripts('', 1, [qr{^$}], $err, 'pgbench option error: ' . $name, + $files); +} + +done_testing(); |