[...]Here is a v10 where I did that when it did not add too much code repetition (eg I kept the shared branches for MIN & MAX and for the 3 RANDOM functions so as to avoid large copy pastes).
Ooops, forgotten attachement. -- Fabien.
diff --git a/doc/src/sgml/ref/pgbench.sgml b/doc/src/sgml/ref/pgbench.sgml index ba3edc4..58ccf5e 100644 --- a/doc/src/sgml/ref/pgbench.sgml +++ b/doc/src/sgml/ref/pgbench.sgml @@ -758,17 +758,20 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> Sets variable <replaceable>varname</> to an integer value calculated from <replaceable>expression</>. The expression may contain integer constants such as <literal>5432</>, - references to variables <literal>:</><replaceable>variablename</>, + double constants such as <literal>3.14156</>, + references to integer variables <literal>:</><replaceable>variablename</>, and expressions composed of unary (<literal>-</>) or binary operators (<literal>+</>, <literal>-</>, <literal>*</>, <literal>/</>, <literal>%</>) - with their usual associativity, and parentheses. + with their usual associativity, function calls and parentheses. + <xref linkend="functions-pgbench-func-table"> shows the available + functions. </para> <para> Examples: <programlisting> \set ntellers 10 * :scale -\set aid (1021 * :aid) % (100000 * :scale) + 1 +\set aid (1021 * random(1, 100000 * :scale)) % (100000 * :scale) + 1 </programlisting></para> </listitem> </varlistentry> @@ -918,18 +921,110 @@ pgbench <optional> <replaceable>options</> </optional> <replaceable>dbname</> </varlistentry> </variablelist> + <!-- list pgbench functions in alphabetical order --> + <table id="functions-pgbench-func-table"> + <title>PgBench Functions</title> + <tgroup cols="5"> + <thead> + <row> + <entry>Function</entry> + <entry>Return Type</entry> + <entry>Description</entry> + <entry>Example</entry> + <entry>Result</entry> + </row> + </thead> + <tbody> + <row> + <entry><literal><function>abs(<replaceable>a</>)</></></> + <entry>same as <replaceable>a</></> + <entry>integer or double absolute value</> + <entry><literal>abs(-17)</></> + <entry><literal>17</></> + </row> + <row> + <entry><literal><function>ddebug(<replaceable>x</>)</></></> + <entry>double</> + <entry>stderr print for debug and return argument</> + <entry><literal>ddebug(5432.1)</></> + <entry><literal>5432.1</></> + </row> + <row> + <entry><literal><function>double(<replaceable>i</>)</></></> + <entry>double</> + <entry>evaluate as int and cast to double</> + <entry><literal>double(5432)</></> + <entry><literal>5432.0</></> + </row> + <row> + <entry><literal><function>exporand(<replaceable>i</>, <replaceable>j</>, <replaceable>t</>)</></></> + <entry>integer</> + <entry>exponentially distributed random integer in the bounds, see below</> + <entry><literal>exporand(1, 10, 3.0)</></> + <entry>int between <literal>1</> and <literal>10</></> + </row> + <row> + <entry><literal><function>idebug(<replaceable>i</>)</></></> + <entry>integer</> + <entry>stderr print for debug and return argument</> + <entry><literal>idebug(5432)</></> + <entry><literal>5432</></> + </row> + <row> + <entry><literal><function>int(<replaceable>x</>)</></></> + <entry>integer</> + <entry>evaluate as double and cast to int</> + <entry><literal>int(5.4 + 3.8)</></> + <entry><literal>9</></> + </row> + <row> + <entry><literal><function>gaussrand(<replaceable>i</>, <replaceable>j</>, <replaceable>t</>)</></></> + <entry>integer</> + <entry>gaussian distributed random integer in the bounds, see below</> + <entry><literal>gaussrand(1, 10, 2.5)</></> + <entry>int between <literal>1</> and <literal>10</></> + </row> + <row> + <entry><literal><function>min(<replaceable>i</>, <replaceable>...</>)</></></> + <entry>integer</> + <entry>minimum value</> + <entry><literal>min(5, 4, 3, 2)</></> + <entry><literal>2</></> + </row> + <row> + <entry><literal><function>max(<replaceable>i</>, <replaceable>...</>)</></></> + <entry>integer</> + <entry>maximum value</> + <entry><literal>max(5, 4, 3, 2)</></> + <entry><literal>5</></> + </row> + <row> + <entry><literal><function>random(<replaceable>i</>, <replaceable>j</>)</></></> + <entry>integer</> + <entry>uniformly distributed random integer in the bounds</> + <entry><literal>random(1, 10)</></> + <entry>int between <literal>1</> and <literal>10</></> + </row> + <row> + <entry><literal><function>sqrt(<replaceable>x</>)</></></> + <entry>double</> + <entry>square root</> + <entry><literal>sqrt(2.0)</></> + <entry><literal>1.414213562</></> + </row> + </tbody> + </tgroup> + </table> + <para> As an example, the full definition of the built-in TPC-B-like transaction is: <programlisting> -\set nbranches :scale -\set ntellers 10 * :scale -\set naccounts 100000 * :scale -\setrandom aid 1 :naccounts -\setrandom bid 1 :nbranches -\setrandom tid 1 :ntellers -\setrandom delta -5000 5000 +\set aid random(1, 100000 * :scale) +\set bid random(1, 1 * :scale) +\set tid random(1, 10 * :scale) +\set delta random(-5000, 5000) BEGIN; UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; SELECT abalance FROM pgbench_accounts WHERE aid = :aid; diff --git a/src/bin/pgbench/exprparse.y b/src/bin/pgbench/exprparse.y index e68631e..97bb559 100644 --- a/src/bin/pgbench/exprparse.y +++ b/src/bin/pgbench/exprparse.y @@ -16,10 +16,14 @@ PgBenchExpr *expr_parse_result; +static PgBenchExprList *make_elist(PgBenchExpr *exp, PgBenchExprList *list); static PgBenchExpr *make_integer_constant(int64 ival); +static PgBenchExpr *make_double_constant(double dval); static PgBenchExpr *make_variable(char *varname); static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr); +static int find_func(const char * fname); +static PgBenchExpr *make_func(const int fnumber, PgBenchExprList *args); %} @@ -29,15 +33,19 @@ static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr, %union { int64 ival; + double dval; char *str; PgBenchExpr *expr; + PgBenchExprList *elist; } +%type <elist> elist %type <expr> expr -%type <ival> INTEGER -%type <str> VARIABLE +%type <ival> INTEGER function +%type <dval> DOUBLE +%type <str> VARIABLE FUNCTION -%token INTEGER VARIABLE +%token INTEGER DOUBLE VARIABLE FUNCTION %token CHAR_ERROR /* never used, will raise a syntax error */ /* Precedence: lowest to highest */ @@ -49,6 +57,10 @@ static PgBenchExpr *make_op(char operator, PgBenchExpr *lexpr, result: expr { expr_parse_result = $1; } +elist: expr { $$ = make_elist($1, NULL); } + | elist ',' expr { $$ = make_elist($3, $1); } + ; + expr: '(' expr ')' { $$ = $2; } | '+' expr %prec UMINUS { $$ = $2; } | '-' expr %prec UMINUS { $$ = make_op('-', make_integer_constant(0), $2); } @@ -58,7 +70,12 @@ expr: '(' expr ')' { $$ = $2; } | expr '/' expr { $$ = make_op('/', $1, $3); } | expr '%' expr { $$ = make_op('%', $1, $3); } | INTEGER { $$ = make_integer_constant($1); } + | DOUBLE { $$ = make_double_constant($1); } | VARIABLE { $$ = make_variable($1); } + | function '(' elist ')'{ $$ = make_func($1, $3); } + ; + +function: FUNCTION { $$ = find_func($1); pg_free($1); } ; %% @@ -74,6 +91,16 @@ make_integer_constant(int64 ival) } static PgBenchExpr * +make_double_constant(double dval) +{ + PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); + + expr->etype = ENODE_DOUBLE_CONSTANT; + expr->u.double_constant.dval = dval; + return expr; +} + +static PgBenchExpr * make_variable(char *varname) { PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); @@ -95,4 +122,93 @@ make_op(char operator, PgBenchExpr *lexpr, PgBenchExpr *rexpr) return expr; } +static struct { + char * fname; + int nargs; + PgBenchFunction tag; +} PGBENCH_FUNCTIONS[] = { + { "abs", 1, PGBENCH_ABS }, + { "sqrt", 1, PGBENCH_SQRT }, + { "int", 1, PGBENCH_INT }, + { "double", 1, PGBENCH_DOUBLE }, + { "min", -1, PGBENCH_MIN }, + { "max", -1, PGBENCH_MAX }, + { "random", 2, PGBENCH_RANDOM }, + { "gaussrand", 3, PGBENCH_GAUSSRAND }, + { "exporand", 3, PGBENCH_EXPORAND }, + { "idebug", 1, PGBENCH_IDEBUG }, + { "ddebug", 1, PGBENCH_DDEBUG } +}; + +static int +find_func(const char * fname) +{ + int nfunctions = sizeof(PGBENCH_FUNCTIONS) / sizeof(PGBENCH_FUNCTIONS[0]); + int i; + + for (i = 0; i < nfunctions; i++) + if (pg_strcasecmp(fname, PGBENCH_FUNCTIONS[i].fname) == 0) + return i; + + expr_yyerror_more("unexpected function name", fname); + + /* not reached */ + return -1; +} + +static PgBenchExprList * +make_elist(PgBenchExpr *expr, PgBenchExprList *list) +{ + PgBenchExprList *cons = pg_malloc(sizeof(PgBenchExprList)); + cons->expr = expr; + cons->next = list; + return cons; +} + +static PgBenchExprList * +reverse_elist(PgBenchExprList *list) +{ + PgBenchExprList *cur = list, *prec = NULL, *next = NULL; + + while (cur != NULL) + { + next = cur->next; + cur->next = prec; + prec = cur; + cur = next; + } + + return prec; +} + +static int +elist_length(PgBenchExprList *list) +{ + int len = 0; + + for (; list != NULL; list = list->next) + len++; + + return len; +} + +static PgBenchExpr * +make_func(const int fnumber, PgBenchExprList *args) +{ + PgBenchExpr *expr = pg_malloc(sizeof(PgBenchExpr)); + + Assert(fnumber >= 0); + + if (PGBENCH_FUNCTIONS[fnumber].nargs != -1 && + PGBENCH_FUNCTIONS[fnumber].nargs != elist_length(args)) + expr_yyerror_more("unexpected number of arguments", + PGBENCH_FUNCTIONS[fnumber].fname); + + expr->etype = ENODE_FUNCTION; + expr->u.function.function = PGBENCH_FUNCTIONS[fnumber].tag; + expr->u.function.args = reverse_elist(args); + + return expr; +} + #include "exprscan.c" diff --git a/src/bin/pgbench/exprscan.l b/src/bin/pgbench/exprscan.l index 5331ab7..bf8aa0f 100644 --- a/src/bin/pgbench/exprscan.l +++ b/src/bin/pgbench/exprscan.l @@ -46,6 +46,7 @@ space [ \t\r\f] "%" { yycol += yyleng; return '%'; } "(" { yycol += yyleng; return '('; } ")" { yycol += yyleng; return ')'; } +"," { yycol += yyleng; return ','; } :[a-zA-Z0-9_]+ { yycol += yyleng; @@ -57,8 +58,19 @@ space [ \t\r\f] yylval.ival = strtoint64(yytext); return INTEGER; } +[0-9]+\.[0-9]+ { + yycol += yyleng; + yylval.dval = atof(yytext); + return DOUBLE; + } +[a-zA-Z]+ { + yycol += yyleng; + yylval.str = pg_strdup(yytext); + return FUNCTION; + } + +[\n] { yycol = 0; yyline++; /* never occurs, input on one line */ } -[\n] { yycol = 0; yyline++; } {space}+ { yycol += yyleng; /* ignore */ } . { @@ -71,10 +83,16 @@ space [ \t\r\f] %% void -yyerror(const char *message) +expr_yyerror_more(const char *message, const char *more) { syntax_error(expr_source, expr_lineno, expr_full_line, expr_command, - message, NULL, expr_col + yycol); + message, more, expr_col + yycol); +} + +void +yyerror(const char *message) +{ + expr_yyerror_more(message, NULL); } /* @@ -94,15 +112,14 @@ expr_scanner_init(const char *str, const char *source, expr_command = (char *) cmd; expr_col = (int) ecol; - /* - * Might be left over after error - */ + /* reset column count for this scan */ + yycol = 0; + + /* Might be left over after error */ if (YY_CURRENT_BUFFER) yy_delete_buffer(YY_CURRENT_BUFFER); - /* - * Make a scan buffer with special termination needed by flex. - */ + /* Make a scan buffer with special termination needed by flex. */ scanbuflen = slen; scanbuf = pg_malloc(slen + 2); memcpy(scanbuf, str, slen); diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 6894345..c66a34b 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -302,13 +302,10 @@ static int debug = 0; /* debug flag */ /* default scenario */ static char *tpc_b = { - "\\set nbranches " CppAsString2(nbranches) " * :scale\n" - "\\set ntellers " CppAsString2(ntellers) " * :scale\n" - "\\set naccounts " CppAsString2(naccounts) " * :scale\n" - "\\setrandom aid 1 :naccounts\n" - "\\setrandom bid 1 :nbranches\n" - "\\setrandom tid 1 :ntellers\n" - "\\setrandom delta -5000 5000\n" + "\\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" @@ -320,13 +317,10 @@ static char *tpc_b = { /* -N case */ static char *simple_update = { - "\\set nbranches " CppAsString2(nbranches) " * :scale\n" - "\\set ntellers " CppAsString2(ntellers) " * :scale\n" - "\\set naccounts " CppAsString2(naccounts) " * :scale\n" - "\\setrandom aid 1 :naccounts\n" - "\\setrandom bid 1 :nbranches\n" - "\\setrandom tid 1 :ntellers\n" - "\\setrandom delta -5000 5000\n" + "\\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" @@ -336,8 +330,7 @@ static char *simple_update = { /* -S case */ static char *select_only = { - "\\set naccounts " CppAsString2(naccounts) " * :scale\n" - "\\setrandom aid 1 :naccounts\n" + "\\set aid random(1, " CppAsString2(naccounts) " * :scale)\n" "SELECT abalance FROM pgbench_accounts WHERE aid = :aid;\n" }; @@ -885,6 +878,123 @@ getQueryParams(CState *st, const Command *command, const char **params) params[i] = getVariable(st, command->argv[i + 1]); } +static bool evalInt(TState *, CState *, PgBenchExpr *, int64 *); + +static bool +evalDouble(TState *thread, CState *st, PgBenchExpr *expr, double *retval) +{ + switch (expr->etype) + { + case ENODE_DOUBLE_CONSTANT: + { + *retval = expr->u.double_constant.dval; + return true; + } + case ENODE_OPERATOR: + { + double lval, rval; + + if (!evalDouble(thread, st, expr->u.operator.lexpr, &lval)) + return false; + if (!evalDouble(thread, st, expr->u.operator.rexpr, &rval)) + return false; + + switch (expr->u.operator.operator) + { + case '+': + *retval = lval + rval; + return true; + + case '-': + *retval = lval - rval; + return true; + + case '*': + *retval = lval * rval; + return true; + + case '/': + *retval = lval / rval; + return true; + + case '%': + default: /* *MUST* be an int operator */ + { + int64 ival; + if (!evalInt(thread, st, expr, &ival)) + return false; + *retval = (double) ival; + return true; + } + } + } + case ENODE_FUNCTION: + { + PgBenchFunction func = expr->u.function.function; + PgBenchExprList *args = expr->u.function.args; + + switch (func) + { + case PGBENCH_ABS: /* also an integer function */ + { + if (!evalDouble(thread, st, args->expr, retval)) + return false; + + if ((*retval) < 0.0) + *retval = - *retval; + + return true; + } + case PGBENCH_SQRT: + { + double arg; + + if (!evalDouble(thread, st, args->expr, &arg)) + return false; + + *retval = sqrt(arg); + + return true; + } + case PGBENCH_DDEBUG: + { + if (!evalDouble(thread, st, args->expr, retval)) + return false; + + fprintf(stderr, "ddebug(script=%d,command=%d): %f\n", + st->use_file, st->state+1, *retval); + + return true; + } + case PGBENCH_DOUBLE: + { + int64 ival; + if (!evalInt(thread, st, args->expr, &ival)) + return false; + *retval = (double) ival; + return true; + } + default: /* *MUST* be an integer function */ + { + int64 ival; + if (!evalInt(thread, st, expr, &ival)) + return false; + *retval = (double) ival; + return true; + } + } + } + default: /* *MUST* be an integer expression */ + { + int64 ival; + if (!evalInt(thread, st, expr, &ival)) + return false; + *retval = (double) ival; + return true; + } + } +} + /* * Recursive evaluation of an expression in a pgbench script * using the current state of variables. @@ -892,7 +1002,7 @@ getQueryParams(CState *st, const Command *command, const char **params) * the value itself is returned through the retval pointer. */ static bool -evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) +evalInt(TState *thread, CState *st, PgBenchExpr *expr, int64 *retval) { switch (expr->etype) { @@ -902,6 +1012,12 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) return true; } + case ENODE_DOUBLE_CONSTANT: + { + *retval = (int64) expr->u.double_constant.dval; + return true; + } + case ENODE_VARIABLE: { char *var; @@ -921,9 +1037,9 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) int64 lval; int64 rval; - if (!evaluateExpr(st, expr->u.operator.lexpr, &lval)) + if (!evalInt(thread, st, expr->u.operator.lexpr, &lval)) return false; - if (!evaluateExpr(st, expr->u.operator.rexpr, &rval)) + if (!evalInt(thread, st, expr->u.operator.rexpr, &rval)) return false; switch (expr->u.operator.operator) { @@ -962,8 +1078,134 @@ evaluateExpr(CState *st, PgBenchExpr *expr, int64 *retval) return false; } - default: - break; + case ENODE_FUNCTION: + { + PgBenchFunction func = expr->u.function.function; + PgBenchExprList *args = expr->u.function.args; + + switch (func) + { + case PGBENCH_RANDOM: + case PGBENCH_GAUSSRAND: + case PGBENCH_EXPORAND: + { + int64 arg1, arg2; + + if (!evalInt(thread, st, args->expr, &arg1)) + return false; + if (!evalInt(thread, st, args->next->expr, &arg2)) + return false; + + /* check random range */ + if (arg1 > arg2) + { + fprintf(stderr, "empty range given to random\n"); + st->ecnt++; + return false; + } + else if (arg2 - arg1 < 0 || (arg2 - arg1) + 1 < 0) + { + /* prevent int overflows in random functions */ + fprintf(stderr, "random range is too large\n"); + st->ecnt++; + return false; + } + + if (func == PGBENCH_RANDOM) + *retval = getrand(thread, arg1, arg2); + else /* gaussian & exponential */ + { + double threshold; + if (!evalDouble(thread, st, args->next->next->expr, + &threshold)) + return false; + if (func == PGBENCH_GAUSSRAND) + *retval = getGaussianRand(thread, arg1, arg2, threshold); + else /* exponential */ + *retval = getExponentialRand(thread, arg1, arg2, threshold); + } + + return true; + } + case PGBENCH_IDEBUG: /* unary functions */ + { + if (!evalInt(thread, st, args->expr, retval)) + return false; + + fprintf(stderr, "idebug(script=%d,command=%d): " + INT64_FORMAT "\n", st->use_file, st->state+1, *retval); + + return true; + } + case PGBENCH_ABS: /* both an int & double function */ + { + if (!evalInt(thread, st, args->expr, retval)) + return false; + + if ((*retval) < 0) + *retval = - *retval; + + return true; + } + case PGBENCH_MIN: /* n-ary, at least one argument */ + case PGBENCH_MAX: + { + int64 val = -1; + bool first = true; + while (args != NULL) + { + int64 arg; + + if (!evalInt(thread, st, args->expr, &arg)) + return false; + + if (first) + val = arg; + else if (func == PGBENCH_MIN) + val = val < arg? val: arg; + else if (func == PGBENCH_MAX) + val = val > arg? val: arg; + + args = args->next; + first = false; + } + + *retval = val; + return true; + } + case PGBENCH_INT: /* eval as double & cast to int */ + { + double arg; + + if (!evalDouble(thread, st, args->expr, &arg)) + return false; + + *retval = (int64) arg; + return true; + } + default: + { + double arg; + + if (!evalDouble(thread, st, expr, &arg)) + return false; + + *retval = (int64) arg; + return true; + } + } + } + + default: /* *MUST* be a double function */ + { + double arg; + + if (!evalDouble(thread, st, expr, &arg)) + return false; + + *retval = (int64) arg; + return true; + } } fprintf(stderr, "bad expression\n"); @@ -1612,7 +1854,7 @@ top: PgBenchExpr *expr = commands[st->state]->expr; int64 result; - if (!evaluateExpr(st, expr, &result)) + if (!evalInt(thread, st, expr, &result)) { st->ecnt++; return true; diff --git a/src/bin/pgbench/pgbench.h b/src/bin/pgbench/pgbench.h index 42e2aae..af7abcb 100644 --- a/src/bin/pgbench/pgbench.h +++ b/src/bin/pgbench/pgbench.h @@ -14,11 +14,30 @@ typedef enum PgBenchExprType { ENODE_INTEGER_CONSTANT, + ENODE_DOUBLE_CONSTANT, ENODE_VARIABLE, - ENODE_OPERATOR + ENODE_OPERATOR, + ENODE_FUNCTION } PgBenchExprType; +typedef enum PgBenchFunction +{ + PGBENCH_NONE, + PGBENCH_INT, + PGBENCH_DOUBLE, + PGBENCH_IDEBUG, + PGBENCH_DDEBUG, + PGBENCH_ABS, + PGBENCH_SQRT, + PGBENCH_MIN, + PGBENCH_MAX, + PGBENCH_RANDOM, + PGBENCH_GAUSSRAND, + PGBENCH_EXPORAND +} PgBenchFunction; + typedef struct PgBenchExpr PgBenchExpr; +typedef struct PgBenchExprList PgBenchExprList; struct PgBenchExpr { @@ -31,6 +50,10 @@ struct PgBenchExpr } integer_constant; struct { + double dval; + } double_constant; + struct + { char *varname; } variable; struct @@ -39,14 +62,25 @@ struct PgBenchExpr PgBenchExpr *lexpr; PgBenchExpr *rexpr; } operator; + struct + { + PgBenchFunction function; + PgBenchExprList *args; + } function; } u; }; +struct PgBenchExprList { + PgBenchExpr *expr; + PgBenchExprList *next; +}; + extern PgBenchExpr *expr_parse_result; extern int expr_yyparse(void); extern int expr_yylex(void); extern void expr_yyerror(const char *str); +extern void expr_yyerror_more(const char *str, const char *more); extern void expr_scanner_init(const char *str, const char *source, const int lineno, const char *line, const char *cmd, const int ecol);
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers