Hello This patch enhances a GET DIAGNOSTICS statement functionality. It adds a possibility of access to exception's data. These data are stored on stack when exception's handler is activated - and these data are access-able everywhere inside handler. It has a different behave (the content is immutable inside handler) and therefore it has modified syntax (use keyword STACKED). This implementation is in conformance with ANSI SQL and SQL/PSM - implemented two standard fields - RETURNED_SQLSTATE and MESSAGE_TEXT and three PostgreSQL specific fields - PG_EXCEPTION_DETAIL, PG_EXCEPTION_HINT and PG_EXCEPTION_CONTEXT.
The GET STACKED DIAGNOSTICS statement is allowed only inside exception's handler. When it is used outside handler, then diagnostics exception 0Z002 is raised. This patch has no impact on performance. It is just interface to existing stacked 'edata' structure. This patch doesn't change a current behave of GET DIAGNOSTICS statement. CREATE OR REPLACE FUNCTION public.stacked_diagnostics_test02() RETURNS void LANGUAGE plpgsql AS $function$ declare _detail text; _hint text; _message text; begin perform ... exception when others then get stacked diagnostics _message = message_text, _detail = pg_exception_detail, _hint = pg_exception_hint; raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; end; $function$ All regress tests was passed. Regards Pavel Stehule
*** ./doc/src/sgml/plpgsql.sgml.orig 2011-05-18 14:22:28.000000000 +0200 --- ./doc/src/sgml/plpgsql.sgml 2011-06-02 09:43:49.682013158 +0200 *************** *** 1387,1393 **** command, which has the form: <synopsis> ! GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional>; </synopsis> This command allows retrieval of system status indicators. Each --- 1387,1393 ---- command, which has the form: <synopsis> ! GET <optional> CURRENT | STACKED </optional> DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replaceable> <optional> , ... </optional>; </synopsis> This command allows retrieval of system status indicators. Each *************** *** 1486,1491 **** --- 1486,1516 ---- affect only the current function. </para> + <para> + Inside a exception handler is possible to use a stacked diagnostics statement. It + allows a access to exception's data: the <varname>RETURNED_SQLSTATE</varname> contains + a SQLSTATE of handled exception. <varname>MESSAGE_TEXT</varname> contains a message text, + <varname>PG_EXCEPTION_DETAIL</varname> has a text that is shown as exception detail, + <varname>PG_EXCEPTION_HINT</varname> has a hint related to catched exception. + <varname>PG_EXCEPTION_CONTEXT</varname> contains a lines that describes call stack. These + variables holds a text value. When some field of exception are not filled, then related + variable contains a empty string, + </para> + + <para> + An example: + <programlisting> + BEGIN + ... + EXCEPTION WHEN OTHERS THEN + GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT, + text_var2 = PG_EXCEPTION_DETAIL, + text_var3 = PG_EXCEPTION_HINT; + END; + </programlisting> + </para> + + </sect2> <sect2 id="plpgsql-statements-null"> *** ./src/backend/utils/errcodes.txt.orig 2011-05-18 14:22:29.000000000 +0200 --- ./src/backend/utils/errcodes.txt 2011-06-01 20:43:16.128831780 +0200 *************** *** 132,137 **** --- 132,141 ---- 0P000 E ERRCODE_INVALID_ROLE_SPECIFICATION invalid_role_specification + Section: Class 0Z - Diagnostics Exception + 0Z000 E ERRCODE_DIAGNOSTICS_EXCEPTION diagnostics_exception + 0Z002 E ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER stacked_diagnostics_accessed_without_active_handler + Section: Class 20 - Case Not Found 20000 E ERRCODE_CASE_NOT_FOUND case_not_found *** ./src/pl/plpgsql/src/gram.y.orig 2011-05-18 19:41:56.755678378 +0200 --- ./src/pl/plpgsql/src/gram.y 2011-06-02 08:52:31.687830966 +0200 *************** *** 206,211 **** --- 206,212 ---- %type <list> getdiag_list %type <diagitem> getdiag_list_item %type <ival> getdiag_item getdiag_target + %type <boolean> getdiag_opt %type <ival> opt_scrollable %type <fetch> opt_fetch_direction *************** *** 250,256 **** --- 251,259 ---- %token <keyword> K_CLOSE %token <keyword> K_COLLATE %token <keyword> K_CONSTANT + %token <keyword> K_CONTEXT %token <keyword> K_CONTINUE + %token <keyword> K_CURRENT %token <keyword> K_CURSOR %token <keyword> K_DEBUG %token <keyword> K_DECLARE *************** *** 263,268 **** --- 266,274 ---- %token <keyword> K_END %token <keyword> K_ERRCODE %token <keyword> K_ERROR + %token <keyword> K_EXCEPTION_CONTEXT + %token <keyword> K_EXCEPTION_DETAIL + %token <keyword> K_EXCEPTION_HINT %token <keyword> K_EXCEPTION %token <keyword> K_EXECUTE %token <keyword> K_EXIT *************** *** 284,289 **** --- 290,296 ---- %token <keyword> K_LOG %token <keyword> K_LOOP %token <keyword> K_MESSAGE + %token <keyword> K_MESSAGE_TEXT %token <keyword> K_MOVE %token <keyword> K_NEXT %token <keyword> K_NO *************** *** 300,311 **** --- 307,320 ---- %token <keyword> K_RELATIVE %token <keyword> K_RESULT_OID %token <keyword> K_RETURN + %token <keyword> K_RETURNED_SQLSTATE %token <keyword> K_REVERSE %token <keyword> K_ROWTYPE %token <keyword> K_ROW_COUNT %token <keyword> K_SCROLL %token <keyword> K_SLICE %token <keyword> K_SQLSTATE + %token <keyword> K_STACKED %token <keyword> K_STRICT %token <keyword> K_THEN %token <keyword> K_TO *************** *** 832,845 **** } ; ! stmt_getdiag : K_GET K_DIAGNOSTICS getdiag_list ';' { PLpgSQL_stmt_getdiag *new; new = palloc0(sizeof(PLpgSQL_stmt_getdiag)); new->cmd_type = PLPGSQL_STMT_GETDIAG; new->lineno = plpgsql_location_to_lineno(@1); ! new->diag_items = $3; $$ = (PLpgSQL_stmt *)new; } --- 841,892 ---- } ; ! stmt_getdiag : K_GET getdiag_opt K_DIAGNOSTICS getdiag_list ';' { PLpgSQL_stmt_getdiag *new; + ListCell *lc; new = palloc0(sizeof(PLpgSQL_stmt_getdiag)); new->cmd_type = PLPGSQL_STMT_GETDIAG; new->lineno = plpgsql_location_to_lineno(@1); ! new->is_stacked = $2; ! new->diag_items = $4; ! ! /* ! * stacked diagnostics statements allows only access ! * to exception data fields. whereas current diagnostics ! * statement disallow access to exception data fields. ! */ ! foreach(lc, $4) ! { ! PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc); ! ! switch (ditem->kind) ! { ! /* these fields are disallowed in stacked diagnostics statement */ ! case PLPGSQL_GETDIAG_ROW_COUNT: ! case PLPGSQL_GETDIAG_RESULT_OID: ! if ($2) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("cannot use ROW_COUNT or RESULT_OID inside stacked diagnostics statemet"), ! parser_errposition(@1))); ! break; ! ! /* these fields are disallowed in current diagnostics statement */ ! case PLPGSQL_GETDIAG_ERROR_CONTEXT: ! case PLPGSQL_GETDIAG_ERROR_DETAIL: ! case PLPGSQL_GETDIAG_ERROR_HINT: ! case PLPGSQL_GETDIAG_RETURNED_SQLSTATE: ! case PLPGSQL_GETDIAG_MESSAGE_TEXT: ! if (!$2) ! ereport(ERROR, ! (errcode(ERRCODE_SYNTAX_ERROR), ! errmsg("EXCEPTION_CONTEXT or EXCEPTION_DETAIL or EXCEPTION_HINT or RETURNED_SQLSTATE or MESSAGE_TEXT are not allowed in current diagnostics statement"), ! parser_errposition(@1))); ! ! } ! } $$ = (PLpgSQL_stmt *)new; } *************** *** 867,872 **** --- 914,933 ---- } ; + getdiag_opt : + { + $$ = false; + } + | K_CURRENT + { + $$ = false; + } + | K_STACKED + { + $$ = true; + } + ; + getdiag_item : { int tok = yylex(); *************** *** 877,882 **** --- 938,958 ---- else if (tok_is_keyword(tok, &yylval, K_RESULT_OID, "result_oid")) $$ = PLPGSQL_GETDIAG_RESULT_OID; + else if (tok_is_keyword(tok, &yylval, + K_EXCEPTION_DETAIL, "pg_exception_detail")) + $$ = PLPGSQL_GETDIAG_ERROR_DETAIL; + else if (tok_is_keyword(tok, &yylval, + K_EXCEPTION_HINT, "pg_exception_hint")) + $$ = PLPGSQL_GETDIAG_ERROR_HINT; + else if (tok_is_keyword(tok, &yylval, + K_EXCEPTION_CONTEXT, "pg_exception_context")) + $$ = PLPGSQL_GETDIAG_ERROR_CONTEXT; + else if (tok_is_keyword(tok, &yylval, + K_MESSAGE_TEXT, "message_text")) + $$ = PLPGSQL_GETDIAG_MESSAGE_TEXT; + else if (tok_is_keyword(tok, &yylval, + K_RETURNED_SQLSTATE, "returned_sqlstate")) + $$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE; else yyerror("unrecognized GET DIAGNOSTICS item"); } *************** *** 2135,2146 **** --- 2211,2226 ---- | K_ARRAY | K_BACKWARD | K_CONSTANT + | K_CURRENT | K_CURSOR | K_DEBUG | K_DETAIL | K_DUMP | K_ERRCODE | K_ERROR + | K_EXCEPTION_CONTEXT + | K_EXCEPTION_DETAIL + | K_EXCEPTION_HINT | K_FIRST | K_FORWARD | K_HINT *************** *** 2149,2154 **** --- 2229,2235 ---- | K_LAST | K_LOG | K_MESSAGE + | K_MESSAGE_TEXT | K_NEXT | K_NO | K_NOTICE *************** *** 2157,2168 **** --- 2238,2251 ---- | K_QUERY | K_RELATIVE | K_RESULT_OID + | K_RETURNED_SQLSTATE | K_REVERSE | K_ROW_COUNT | K_ROWTYPE | K_SCROLL | K_SLICE | K_SQLSTATE + | K_STACKED | K_TYPE | K_USE_COLUMN | K_USE_VARIABLE *** ./src/pl/plpgsql/src/pl_exec.c.orig 2011-05-18 19:42:15.458152167 +0200 --- ./src/pl/plpgsql/src/pl_exec.c 2011-06-01 21:15:39.903684130 +0200 *************** *** 1449,1454 **** --- 1449,1506 ---- ObjectIdGetDatum(estate->eval_lastoid), OIDOID, &isnull); break; + case PLPGSQL_GETDIAG_ERROR_DETAIL: + case PLPGSQL_GETDIAG_ERROR_HINT: + case PLPGSQL_GETDIAG_ERROR_CONTEXT: + case PLPGSQL_GETDIAG_RETURNED_SQLSTATE: + case PLPGSQL_GETDIAG_MESSAGE_TEXT: + { + char *strval = NULL; + Datum value; + + /* + * Now a fields based on processing of Error Data + * are handled. + */ + if (estate->cur_error == NULL) + ereport(ERROR, + (errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER), + errmsg("stacked diagnostics accessed without active handler"))); + switch (diag_item->kind) + { + case PLPGSQL_GETDIAG_ERROR_DETAIL: + strval = estate->cur_error->detail; + break; + case PLPGSQL_GETDIAG_ERROR_HINT: + strval = estate->cur_error->hint; + break; + case PLPGSQL_GETDIAG_ERROR_CONTEXT: + strval = estate->cur_error->context; + break; + case PLPGSQL_GETDIAG_RETURNED_SQLSTATE: + strval = unpack_sql_state(estate->cur_error->sqlerrcode); + break; + case PLPGSQL_GETDIAG_MESSAGE_TEXT: + strval = estate->cur_error->message; + } + + if (strval != NULL) + { + value = PointerGetDatum(cstring_to_text(strval)); + } + else + { + /* + * Diagnostics fields are never NULL, it should to return empty string + * instead. + */ + value = PointerGetDatum(cstring_to_text("")); + } + + exec_assign_value(estate, var, + value, TEXTOID, &isnull); + break; + } default: elog(ERROR, "unrecognized attribute request: %d", *** ./src/pl/plpgsql/src/pl_funcs.c.orig 2011-05-21 20:59:52.557887117 +0200 --- ./src/pl/plpgsql/src/pl_funcs.c 2011-06-02 08:39:56.248144435 +0200 *************** *** 1389,1395 **** ListCell *lc; dump_ind(); ! printf("GET DIAGNOSTICS "); foreach(lc, stmt->diag_items) { PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); --- 1389,1395 ---- ListCell *lc; dump_ind(); ! printf("GET %s DIAGNOSTICS ", stmt->is_stacked ? "STACKED" : "CURRENT"); foreach(lc, stmt->diag_items) { PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc); *************** *** 1409,1414 **** --- 1409,1434 ---- printf("RESULT_OID"); break; + case PLPGSQL_GETDIAG_ERROR_CONTEXT: + printf("PG_EXCEPTION_CONTEXT"); + break; + + case PLPGSQL_GETDIAG_ERROR_DETAIL: + printf("PG_EXCEPTION_DETAIL"); + break; + + case PLPGSQL_GETDIAG_ERROR_HINT: + printf("PG_EXCEPTION_HINT"); + break; + + case PLPGSQL_GETDIAG_MESSAGE_TEXT: + printf("MESSAGE_TEXT"); + break; + + case PLPGSQL_GETDIAG_RETURNED_SQLSTATE: + printf("RETURNED_SQLSTATE"); + break; + default: printf("???"); break; *** ./src/pl/plpgsql/src/plpgsql.h.orig 2011-05-18 19:42:06.402954431 +0200 --- ./src/pl/plpgsql/src/plpgsql.h 2011-06-01 19:21:55.444895044 +0200 *************** *** 126,132 **** enum { PLPGSQL_GETDIAG_ROW_COUNT, ! PLPGSQL_GETDIAG_RESULT_OID }; /* -------- --- 126,137 ---- enum { PLPGSQL_GETDIAG_ROW_COUNT, ! PLPGSQL_GETDIAG_RESULT_OID, ! PLPGSQL_GETDIAG_ERROR_CONTEXT, ! PLPGSQL_GETDIAG_ERROR_DETAIL, ! PLPGSQL_GETDIAG_ERROR_HINT, ! PLPGSQL_GETDIAG_RETURNED_SQLSTATE, ! PLPGSQL_GETDIAG_MESSAGE_TEXT }; /* -------- *************** *** 321,326 **** --- 326,334 ---- { /* One EXCEPTION condition name */ int sqlerrstate; /* SQLSTATE code */ char *condname; /* condition name (for debugging) */ + bool needs_edata; /* needs access to edata content */ + bool needs_stacked_edata; /* needs push content of edata to diagnostics stack */ + bool needs_evaluate_context; /* needs to materialize a function context */ struct PLpgSQL_condition *next; } PLpgSQL_condition; *************** *** 377,382 **** --- 385,391 ---- int cmd_type; int lineno; List *diag_items; /* List of PLpgSQL_diag_item */ + bool is_stacked; /* true when it access a second area of diagnostics info */ } PLpgSQL_stmt_getdiag; *** ./src/pl/plpgsql/src/pl_scanner.c.orig 2011-05-18 14:22:29.000000000 +0200 --- ./src/pl/plpgsql/src/pl_scanner.c 2011-06-01 21:18:47.632566225 +0200 *************** *** 110,115 **** --- 110,116 ---- PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD) PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD) + PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD) PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD) PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD) PG_KEYWORD("detail", K_DETAIL, UNRESERVED_KEYWORD) *************** *** 124,143 **** --- 125,150 ---- PG_KEYWORD("last", K_LAST, UNRESERVED_KEYWORD) PG_KEYWORD("log", K_LOG, UNRESERVED_KEYWORD) PG_KEYWORD("message", K_MESSAGE, UNRESERVED_KEYWORD) + PG_KEYWORD("message_text", K_MESSAGE_TEXT, UNRESERVED_KEYWORD) PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD) PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD) PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD) PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD) + PG_KEYWORD("pg_exception_context", K_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD) + PG_KEYWORD("pg_exception_detail", K_EXCEPTION_DETAIL, UNRESERVED_KEYWORD) + PG_KEYWORD("pg_exception_hint", K_EXCEPTION_HINT, UNRESERVED_KEYWORD) PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD) PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD) PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD) PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD) + PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE, UNRESERVED_KEYWORD) PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD) PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD) PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD) PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD) PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD) PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD) + PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD) PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD) PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD) PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD) *** ./src/test/regress/expected/plpgsql.out.orig 2011-05-18 14:22:29.000000000 +0200 --- ./src/test/regress/expected/plpgsql.out 2011-06-02 09:58:34.000000000 +0200 *************** *** 4434,4436 **** --- 4434,4511 ---- drop function foreach_test(anyarray); drop type xy_tuple; + -- access to exception data + create or replace function bad_function() + returns int as $$ + declare v int := 0; + begin + return 10 / v; + end; + $$ language plpgsql; + create or replace function raise_function() + returns void as $$ + begin + raise exception 'custom exception' + using detail = 'some detail of custom exception', + hint = 'some hint related to custom exception'; + end; + $$ language plpgsql; + create or replace function stacked_diagnostics_test01() + returns void as $$ + declare _sqlstate text; _message text; _context text; + begin + perform bad_function(); + exception when others then + get stacked diagnostics + _sqlstate = returned_sqlstate, + _message = message_text, + _context = pg_exception_context; + raise notice 'sqlstate: %, message: %, context: [%]', _sqlstate, _message, replace(_context, e'\n', ' <- '); + end; + $$ language plpgsql; + select stacked_diagnostics_test01(); + NOTICE: sqlstate: 22012, message: division by zero, context: [PL/pgSQL function "bad_function" line 4 at RETURN <- SQL statement "SELECT bad_function()" <- PL/pgSQL function "stacked_diagnostics_test01" line 4 at PERFORM] + stacked_diagnostics_test01 + ---------------------------- + + (1 row) + + create or replace function stacked_diagnostics_test02() + returns void as $$ + declare _detail text; _hint text; _message text; + begin + perform raise_function(); + exception when others then + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; + end; + $$ language plpgsql; + select stacked_diagnostics_test02(); + NOTICE: message: custom exception, detail: some detail of custom exception, hint: some hint related to custom exception + stacked_diagnostics_test02 + ---------------------------- + + (1 row) + + -- should to fail, cannot use stacked diagnostics statement outside handler + create or replace function stacked_diagnostics_test02() + returns void as $$ + declare _detail text; _hint text; _message text; + begin + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; + end; + $$ language plpgsql; + select stacked_diagnostics_test02(); + ERROR: stacked diagnostics accessed without active handler + CONTEXT: PL/pgSQL function "stacked_diagnostics_test02" line 4 at GET DIAGNOSTICS + drop function bad_function(); + drop function raise_function(); + drop function stacked_diagnostics_test01(); + drop function stacked_diagnostics_test02(); *** ./src/test/regress/sql/plpgsql.sql.orig 2011-05-18 14:22:30.000000000 +0200 --- ./src/test/regress/sql/plpgsql.sql 2011-06-02 09:57:22.040084273 +0200 *************** *** 3489,3491 **** --- 3489,3563 ---- drop function foreach_test(anyarray); drop type xy_tuple; + + + -- access to exception data + create or replace function bad_function() + returns int as $$ + declare v int := 0; + begin + return 10 / v; + end; + $$ language plpgsql; + + create or replace function raise_function() + returns void as $$ + begin + raise exception 'custom exception' + using detail = 'some detail of custom exception', + hint = 'some hint related to custom exception'; + end; + $$ language plpgsql; + + create or replace function stacked_diagnostics_test01() + returns void as $$ + declare _sqlstate text; _message text; _context text; + begin + perform bad_function(); + exception when others then + get stacked diagnostics + _sqlstate = returned_sqlstate, + _message = message_text, + _context = pg_exception_context; + raise notice 'sqlstate: %, message: %, context: [%]', _sqlstate, _message, replace(_context, e'\n', ' <- '); + end; + $$ language plpgsql; + + select stacked_diagnostics_test01(); + + create or replace function stacked_diagnostics_test02() + returns void as $$ + declare _detail text; _hint text; _message text; + begin + perform raise_function(); + exception when others then + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; + end; + $$ language plpgsql; + + select stacked_diagnostics_test02(); + + -- should to fail, cannot use stacked diagnostics statement outside handler + + create or replace function stacked_diagnostics_test02() + returns void as $$ + declare _detail text; _hint text; _message text; + begin + get stacked diagnostics + _message = message_text, + _detail = pg_exception_detail, + _hint = pg_exception_hint; + raise notice 'message: %, detail: %, hint: %', _message, _detail, _hint; + end; + $$ language plpgsql; + + select stacked_diagnostics_test02(); + + drop function bad_function(); + drop function raise_function(); + drop function stacked_diagnostics_test01(); + drop function stacked_diagnostics_test02();
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers