From: Daniel Gustafsson =0A= Date: Sun, 3 Sep 2017 22:43:16 +0200=0A= Subject: [PATCH] Support optional message in backend cancel/terminate=0A= =0A= This adds the ability for the caller of pg_terminate_backend() or=0A= pg_cancel_backend() to include an optional message to the process=0A= which is being signalled. The message will be appended to the error=0A= message returned to the killed or cancelled process. The new syntax=0A= overloaded the existing as:=0A= =0A= SELECT pg_terminate_backend( [, msg]);=0A= SELECT pg_cancel_backend( [, msg]);=0A= ---=0A= doc/src/sgml/func.sgml | 6 +-=0A= src/backend/storage/ipc/ipci.c | 3 +=0A= src/backend/tcop/postgres.c | 38 ++++++-=0A= src/backend/utils/adt/misc.c | 60 ++++++++--=0A= src/backend/utils/init/postinit.c | 2 +=0A= src/backend/utils/misc/Makefile | 6 +-=0A= src/backend/utils/misc/backend_cancel.c | 189 ++++++++++++++++++++++++++++= ++++=0A= src/include/catalog/pg_proc.h | 4 +=0A= src/include/utils/backend_cancel.h | 25 +++++=0A= 9 files changed, 314 insertions(+), 19 deletions(-)=0A= create mode 100644 src/backend/utils/misc/backend_cancel.c=0A= create mode 100644 src/include/utils/backend_cancel.h=0A= =0A= diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml=0A= index 641b3b8f4e..ecde80aad2 100644=0A= --- a/doc/src/sgml/func.sgml=0A= +++ b/doc/src/sgml/func.sgml=0A= @@ -18359,7 +18359,7 @@ SELECT set_config('log_statement_stats', 'off', fal= se);=0A= =0A= =0A= =0A= - pg_cancel_backend(pid int)=0A= + pg_cancel_backend(pid int [, message text])=0A= =0A= boolean=0A= Cancel a backend's current query. This is also allowed if t= he=0A= @@ -18384,7 +18384,7 @@ SELECT set_config('log_statement_stats', 'off', fal= se);=0A= =0A= =0A= =0A= - pg_terminate_backend(pid= int)=0A= + pg_terminate_backend(pid= int [, message text])<= /literal>=0A= =0A= boolean=0A= Terminate a backend. This is also allowed if the calling ro= le=0A= @@ -18415,6 +18415,8 @@ SELECT set_config('log_statement_stats', 'off', fal= se);=0A= The role of an active backend can be found from the=0A= usename column of the=0A= pg_stat_activity view.=0A= + If the optional message parameter is set, the text= =0A= + will be appended to the error message returned to the signalled backen= d.=0A= =0A= =20=0A= =0A= diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.= c=0A= index 2d1ed143e0..f998ee82d3 100644=0A= --- a/src/backend/storage/ipc/ipci.c=0A= +++ b/src/backend/storage/ipc/ipci.c=0A= @@ -44,6 +44,7 @@=0A= #include "storage/procsignal.h"=0A= #include "storage/sinvaladt.h"=0A= #include "storage/spin.h"=0A= +#include "utils/backend_cancel.h"=0A= #include "utils/backend_random.h"=0A= #include "utils/snapmgr.h"=0A= =20=0A= @@ -150,6 +151,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int p= ort)=0A= size =3D add_size(size, SyncScanShmemSize());=0A= size =3D add_size(size, AsyncShmemSize());=0A= size =3D add_size(size, BackendRandomShmemSize());=0A= + size =3D add_size(size, CancelBackendMsgShmemSize());=0A= #ifdef EXEC_BACKEND=0A= size =3D add_size(size, ShmemBackendArraySize());=0A= #endif=0A= @@ -270,6 +272,7 @@ CreateSharedMemoryAndSemaphores(bool makePrivate, int p= ort)=0A= SyncScanShmemInit();=0A= AsyncShmemInit();=0A= BackendRandomShmemInit();=0A= + BackendCancelShmemInit();=0A= =20=0A= #ifdef EXEC_BACKEND=0A= =20=0A= diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c=0A= index 8d3fecf6d6..248cd84ee5 100644=0A= --- a/src/backend/tcop/postgres.c=0A= +++ b/src/backend/tcop/postgres.c=0A= @@ -69,6 +69,7 @@=0A= #include "tcop/pquery.h"=0A= #include "tcop/tcopprot.h"=0A= #include "tcop/utility.h"=0A= +#include "utils/backend_cancel.h"=0A= #include "utils/lsyscache.h"=0A= #include "utils/memutils.h"=0A= #include "utils/ps_status.h"=0A= @@ -2879,9 +2880,22 @@ ProcessInterrupts(void)=0A= errdetail_recovery_conflict()));=0A= }=0A= else=0A= - ereport(FATAL,=0A= - (errcode(ERRCODE_ADMIN_SHUTDOWN),=0A= - errmsg("terminating connection due to administrator command")));=0A= + {=0A= + if (HasCancelMessage())=0A= + {=0A= + char *buffer =3D palloc0(MAX_CANCEL_MSG);=0A= +=0A= + GetCancelMessage(&buffer, MAX_CANCEL_MSG);=0A= + ereport(FATAL,=0A= + (errcode(ERRCODE_ADMIN_SHUTDOWN),=0A= + errmsg("terminating connection due to administrator command: \"%s\"= ",=0A= + buffer)));=0A= + }=0A= + else=0A= + ereport(FATAL,=0A= + (errcode(ERRCODE_ADMIN_SHUTDOWN),=0A= + errmsg("terminating connection due to administrator command")));=0A= + }=0A= }=0A= if (ClientConnectionLost)=0A= {=0A= @@ -2994,9 +3008,21 @@ ProcessInterrupts(void)=0A= if (!DoingCommandRead)=0A= {=0A= LockErrorCleanup();=0A= - ereport(ERROR,=0A= - (errcode(ERRCODE_QUERY_CANCELED),=0A= - errmsg("canceling statement due to user request")));=0A= +=0A= + if (HasCancelMessage())=0A= + {=0A= + char *buffer =3D palloc0(MAX_CANCEL_MSG);=0A= +=0A= + GetCancelMessage(&buffer, MAX_CANCEL_MSG);=0A= + ereport(ERROR,=0A= + (errcode(ERRCODE_QUERY_CANCELED),=0A= + errmsg("canceling statement due to user request: \"%s\"",=0A= + buffer)));=0A= + }=0A= + else=0A= + ereport(ERROR,=0A= + (errcode(ERRCODE_QUERY_CANCELED),=0A= + errmsg("canceling statement due to user request")));=0A= }=0A= }=0A= =20=0A= diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c=0A= index 62341b84d1..38d4fd8ad9 100644=0A= --- a/src/backend/utils/adt/misc.c=0A= +++ b/src/backend/utils/adt/misc.c=0A= @@ -41,6 +41,7 @@=0A= #include "utils/ruleutils.h"=0A= #include "tcop/tcopprot.h"=0A= #include "utils/acl.h"=0A= +#include "utils/backend_cancel.h"=0A= #include "utils/builtins.h"=0A= #include "utils/timestamp.h"=0A= =20=0A= @@ -216,7 +217,7 @@ current_query(PG_FUNCTION_ARGS)=0A= #define SIGNAL_BACKEND_NOPERMISSION 2=0A= #define SIGNAL_BACKEND_NOSUPERUSER 3=0A= static int=0A= -pg_signal_backend(int pid, int sig)=0A= +pg_signal_backend(int pid, int sig, char *msg)=0A= {=0A= PGPROC *proc =3D BackendPidGetProc(pid);=0A= =20=0A= @@ -248,6 +249,18 @@ pg_signal_backend(int pid, int sig)=0A= !has_privs_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID))=0A= return SIGNAL_BACKEND_NOPERMISSION;=0A= =20=0A= + /* If the user supplied a message to the signalled backend */=0A= + if (msg !=3D NULL)=0A= + {=0A= + int r;=0A= +=0A= + r =3D SetBackendCancelMessage(pid, msg);=0A= +=0A= + if (r !=3D -1 && r !=3D strlen(msg))=0A= + ereport(NOTICE,=0A= + (errmsg("message is too long and has been truncated")));=0A= + }=0A= +=0A= /*=0A= * Can the process we just validated above end, followed by the pid being= =0A= * recycled for a new process, before reaching here? Then we'd be trying= =0A= @@ -278,10 +291,10 @@ pg_signal_backend(int pid, int sig)=0A= *=0A= * Note that only superusers can signal superuser-owned processes.=0A= */=0A= -Datum=0A= -pg_cancel_backend(PG_FUNCTION_ARGS)=0A= +static bool=0A= +pg_cancel_backend_internal(pid_t pid, char *msg)=0A= {=0A= - int r =3D pg_signal_backend(PG_GETARG_INT32(0), SIGINT);=0A= + int r =3D pg_signal_backend(pid, SIGINT, msg);=0A= =20=0A= if (r =3D=3D SIGNAL_BACKEND_NOSUPERUSER)=0A= ereport(ERROR,=0A= @@ -296,16 +309,32 @@ pg_cancel_backend(PG_FUNCTION_ARGS)=0A= PG_RETURN_BOOL(r =3D=3D SIGNAL_BACKEND_SUCCESS);=0A= }=0A= =20=0A= +Datum=0A= +pg_cancel_backend(PG_FUNCTION_ARGS)=0A= +{=0A= + PG_RETURN_BOOL(pg_cancel_backend_internal(PG_GETARG_INT32(0), NULL));=0A= +}=0A= +=0A= +Datum=0A= +pg_cancel_backend_msg(PG_FUNCTION_ARGS)=0A= +{=0A= + pid_t pid =3D PG_GETARG_INT32(0);=0A= + char *msg =3D text_to_cstring(PG_GETARG_TEXT_PP(1));=0A= +=0A= + PG_RETURN_BOOL(pg_cancel_backend_internal(pid, msg));=0A= +}=0A= +=0A= +=0A= /*=0A= * Signal to terminate a backend process. This is allowed if you are a me= mber=0A= * of the role whose process is being terminated.=0A= *=0A= * Note that only superusers can signal superuser-owned processes.=0A= */=0A= -Datum=0A= -pg_terminate_backend(PG_FUNCTION_ARGS)=0A= +static bool=0A= +pg_terminate_backend_internal(pid_t pid, char *msg)=0A= {=0A= - int r =3D pg_signal_backend(PG_GETARG_INT32(0), SIGTERM);=0A= + int r =3D pg_signal_backend(pid, SIGTERM, msg);=0A= =20=0A= if (r =3D=3D SIGNAL_BACKEND_NOSUPERUSER)=0A= ereport(ERROR,=0A= @@ -317,7 +346,22 @@ pg_terminate_backend(PG_FUNCTION_ARGS)=0A= (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),=0A= (errmsg("must be a member of the role whose process is being terminat= ed or member of pg_signal_backend"))));=0A= =20=0A= - PG_RETURN_BOOL(r =3D=3D SIGNAL_BACKEND_SUCCESS);=0A= + return (r =3D=3D SIGNAL_BACKEND_SUCCESS);=0A= +}=0A= +=0A= +Datum=0A= +pg_terminate_backend(PG_FUNCTION_ARGS)=0A= +{=0A= + PG_RETURN_BOOL(pg_terminate_backend_internal(PG_GETARG_INT32(0), NULL));= =0A= +}=0A= +=0A= +Datum=0A= +pg_terminate_backend_msg(PG_FUNCTION_ARGS)=0A= +{=0A= + pid_t pid =3D PG_GETARG_INT32(0);=0A= + char *msg =3D text_to_cstring(PG_GETARG_TEXT_PP(1));=0A= +=0A= + PG_RETURN_BOOL(pg_terminate_backend_internal(pid, msg));=0A= }=0A= =20=0A= /*=0A= diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/pos= tinit.c=0A= index eb6960d93f..c2abaf34ee 100644=0A= --- a/src/backend/utils/init/postinit.c=0A= +++ b/src/backend/utils/init/postinit.c=0A= @@ -50,6 +50,7 @@=0A= #include "storage/smgr.h"=0A= #include "tcop/tcopprot.h"=0A= #include "utils/acl.h"=0A= +#include "utils/backend_cancel.h"=0A= #include "utils/fmgroids.h"=0A= #include "utils/guc.h"=0A= #include "utils/memutils.h"=0A= @@ -740,6 +741,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const ch= ar *username,=0A= PerformAuthentication(MyProcPort);=0A= InitializeSessionUserId(username, useroid);=0A= am_superuser =3D superuser();=0A= + BackendCancelInit(MyBackendId);=0A= }=0A= =20=0A= /*=0A= diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makef= ile=0A= index a53fcdf188..619c837e08 100644=0A= --- a/src/backend/utils/misc/Makefile=0A= +++ b/src/backend/utils/misc/Makefile=0A= @@ -14,9 +14,9 @@ include $(top_builddir)/src/Makefile.global=0A= =20=0A= override CPPFLAGS :=3D -I. -I$(srcdir) $(CPPFLAGS)=0A= =20=0A= -OBJS =3D backend_random.o guc.o help_config.o pg_config.o pg_controldata.o= \=0A= - pg_rusage.o ps_status.o queryenvironment.o rls.o sampling.o \=0A= - superuser.o timeout.o tzparser.o=0A= +OBJS =3D backend_cancel.o backend_random.o guc.o help_config.o pg_config.o= \=0A= + pg_controldata.o pg_rusage.o ps_status.o queryenvironment.o rls.o \= =0A= + sampling.o superuser.o timeout.o tzparser.o=0A= =20=0A= # This location might depend on the installation directories. Therefore=0A= # we can't substitute it into pg_config.h.=0A= diff --git a/src/backend/utils/misc/backend_cancel.c b/src/backend/utils/mi= sc/backend_cancel.c=0A= new file mode 100644=0A= index 0000000000..194554eab2=0A= --- /dev/null=0A= +++ b/src/backend/utils/misc/backend_cancel.c=0A= @@ -0,0 +1,189 @@=0A= +/*------------------------------------------------------------------------= -=0A= + *=0A= + * backend_cancel.c=0A= + * Backend cancellation messaging=0A= + *=0A= + *=0A= + * Module for supporting passing a user defined message to a cancelled,=0A= + * or terminated, backend from the user/administrator.=0A= + *=0A= + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group= =0A= + * Portions Copyright (c) 1994, Regents of the University of California=0A= + *=0A= + * IDENTIFICATION=0A= + * src/backend/utils/misc/backend_cancel.c=0A= + *=0A= + *------------------------------------------------------------------------= -=0A= + */=0A= +#include "postgres.h"=0A= +=0A= +#include "miscadmin.h"=0A= +#include "storage/ipc.h"=0A= +#include "storage/spin.h"=0A= +#include "storage/shmem.h"=0A= +#include "utils/backend_cancel.h"=0A= +=0A= +/*=0A= + * Each backend is registered per pid in the array which is indexed by Bac= kend=0A= + * ID. Reading and writing the message is protected by a per-slot spinlock= .=0A= + */=0A= +typedef struct=0A= +{=0A= + pid_t pid;=0A= + slock_t mutex;=0A= + char message[MAX_CANCEL_MSG];=0A= + int len;=0A= +} BackendCancelShmemStruct;=0A= +=0A= +static BackendCancelShmemStruct *BackendCancelSlots =3D NULL;=0A= +static volatile BackendCancelShmemStruct *MyCancelSlot =3D NULL;=0A= +=0A= +static void CleanupCancelBackend(int status, Datum argument);=0A= +=0A= +Size=0A= +CancelBackendMsgShmemSize(void)=0A= +{=0A= + return MaxBackends * sizeof(BackendCancelShmemStruct);=0A= +}=0A= +=0A= +void=0A= +BackendCancelShmemInit(void)=0A= +{=0A= + Size size =3D CancelBackendMsgShmemSize();=0A= + bool found;=0A= + int i;=0A= +=0A= + BackendCancelSlots =3D (BackendCancelShmemStruct *)=0A= + ShmemInitStruct("BackendCancelSlots", size, &found);=0A= +=0A= + if (!found)=0A= + {=0A= + MemSet(BackendCancelSlots, 0, size);=0A= +=0A= + for (i =3D 0; i < MaxBackends; i++)=0A= + SpinLockInit(&(BackendCancelSlots[i].mutex));=0A= + }=0A= +}=0A= +=0A= +void=0A= +BackendCancelInit(int backend_id)=0A= +{=0A= + volatile BackendCancelShmemStruct *slot;=0A= +=0A= + slot =3D &BackendCancelSlots[backend_id - 1];=0A= +=0A= + slot->message[0] =3D '\0';=0A= + slot->len =3D 0;=0A= + slot->pid =3D MyProcPid;=0A= +=0A= + MyCancelSlot =3D slot;=0A= +=0A= + on_shmem_exit(CleanupCancelBackend, Int32GetDatum(backend_id));=0A= +}=0A= +=0A= +static void=0A= +CleanupCancelBackend(int status, Datum argument)=0A= +{=0A= + int backend_id =3D DatumGetInt32(argument);=0A= + volatile BackendCancelShmemStruct *slot;=0A= +=0A= + slot =3D &BackendCancelSlots[backend_id - 1];=0A= +=0A= + Assert(slot =3D=3D MyCancelSlot);=0A= +=0A= + MyCancelSlot =3D NULL;=0A= +=0A= + if (slot->len > 0)=0A= + slot->message[0] =3D '\0';=0A= +=0A= + slot->len =3D 0;=0A= + slot->pid =3D 0;=0A= +}=0A= +=0A= +/*=0A= + * Sets a cancellation message for the backend with the specified pid, and= =0A= + * returns the length of message actually created. If the returned length= =0A= + * is less than the length of the message parameter, truncation has occurr= ed.=0A= + * If the backend wasn't found and no message was set, -1 is returned. If = two=0A= + * backends collide in setting a message, the existing message will be=0A= + * overwritten by the last one in.=0A= + */=0A= +int=0A= +SetBackendCancelMessage(pid_t backend, char *message)=0A= +{=0A= + BackendCancelShmemStruct *slot;=0A= + int i;=0A= + int message_len;=0A= +=0A= + if (!message)=0A= + return 0;=0A= +=0A= + for (i =3D 0; i < MaxBackends; i++)=0A= + {=0A= + slot =3D &BackendCancelSlots[i];=0A= +=0A= + if (slot->pid !=3D 0 && slot->pid =3D=3D backend)=0A= + {=0A= + SpinLockAcquire(&slot->mutex);=0A= + if (slot->pid !=3D backend)=0A= + {=0A= + SpinLockRelease(&slot->mutex);=0A= + goto error;=0A= + }=0A= +=0A= + strlcpy(slot->message, message, sizeof(slot->message));=0A= + slot->len =3D strlen(slot->message);=0A= + message_len =3D slot->len;=0A= + SpinLockRelease(&slot->mutex);=0A= +=0A= + return message_len;=0A= + }=0A= + }=0A= +=0A= +error:=0A= +=0A= + elog(LOG, "Cancellation message requested for missing backend %d by %d",= =0A= + (int) backend, MyProcPid);=0A= +=0A= + return -1;=0A= +}=0A= +=0A= +bool=0A= +HasCancelMessage(void)=0A= +{=0A= + volatile BackendCancelShmemStruct *slot =3D MyCancelSlot;=0A= + bool has_message =3D false;=0A= +=0A= + if (slot !=3D NULL)=0A= + {=0A= + SpinLockAcquire(&slot->mutex);=0A= + has_message =3D (slot->len > 0);=0A= + SpinLockRelease(&slot->mutex);=0A= + }=0A= +=0A= + return has_message;=0A= +}=0A= +=0A= +/*=0A= + * Return the configured cancellation message and its length. If the retur= ned=0A= + * length is greater than the size of the passed buffer, truncation has be= en=0A= + * performed. The message is cleared on reading.=0A= + */=0A= +int=0A= +GetCancelMessage(char **buffer, size_t buf_len)=0A= +{=0A= + volatile BackendCancelShmemStruct *slot =3D MyCancelSlot;=0A= + int msg_length =3D 0;=0A= +=0A= + if (slot !=3D NULL && slot->len > 0)=0A= + {=0A= + SpinLockAcquire(&slot->mutex);=0A= + strlcpy(*buffer, (const char *) slot->message, buf_len);=0A= + msg_length =3D slot->len;=0A= + slot->len =3D 0;=0A= + slot->message[0] =3D '\0';=0A= + SpinLockRelease(&slot->mutex);=0A= + }=0A= +=0A= + return msg_length;=0A= +}=0A= diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h= =0A= index d820b56aa1..ceeb85e13d 100644=0A= --- a/src/include/catalog/pg_proc.h=0A= +++ b/src/include/catalog/pg_proc.h=0A= @@ -3247,8 +3247,12 @@ DESCR("is schema another session's temp schema?");= =0A= =20=0A= DATA(insert OID =3D 2171 ( pg_cancel_backend PGNSP PGUID 12 1 0 0 0 f f f= f t f v s 1 0 16 "23" _null_ _null_ _null_ _null_ _null_ pg_cancel_backend= _null_ _null_ _null_ ));=0A= DESCR("cancel a server process' current query");=0A= +DATA(insert OID =3D 3438 ( pg_cancel_backend PGNSP PGUID 12 1 0 0 0 f f f= f t f v s 2 0 16 "23 25" _null_ _null_ _null_ _null_ _null_ pg_cancel_back= end_msg _null_ _null_ _null_ ));=0A= +DESCR("cancel a server process' current query");=0A= DATA(insert OID =3D 2096 ( pg_terminate_backend PGNSP PGUID 12 1 0 0 0 f = f f f t f v s 1 0 16 "23" _null_ _null_ _null_ _null_ _null_ pg_terminate_b= ackend _null_ _null_ _null_ ));=0A= DESCR("terminate a server process");=0A= +DATA(insert OID =3D 3437 ( pg_terminate_backend PGNSP PGUID 12 1 0 0 0 f = f f f t f v s 2 0 16 "23 25" _null_ _null_ _null_ _null_ _null_ pg_terminat= e_backend_msg _null_ _null_ _null_ ));=0A= +DESCR("terminate a server process");=0A= DATA(insert OID =3D 2172 ( pg_start_backup PGNSP PGUID 12 1 0 0 0 f f f f= t f v r 3 0 3220 "25 16 16" _null_ _null_ _null_ _null_ _null_ pg_start_ba= ckup _null_ _null_ _null_ ));=0A= DESCR("prepare for taking an online backup");=0A= DATA(insert OID =3D 2173 ( pg_stop_backup PGNSP PGUID 12 1 0 0 0 f f f f= t f v r 0 0 3220 "" _null_ _null_ _null_ _null_ _null_ pg_stop_backup _nul= l_ _null_ _null_ ));=0A= diff --git a/src/include/utils/backend_cancel.h b/src/include/utils/backend= _cancel.h=0A= new file mode 100644=0A= index 0000000000..7f210553d6=0A= --- /dev/null=0A= +++ b/src/include/utils/backend_cancel.h=0A= @@ -0,0 +1,25 @@=0A= +/*------------------------------------------------------------------------= -=0A= + *=0A= + * backend_cancel.h=0A= + * Declarations for backend cancellation messaging=0A= + *=0A= + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group= =0A= + *=0A= + * src/include/utils/backend_cancel.h=0A= + *=0A= + *------------------------------------------------------------------------= -=0A= + */=0A= +#ifndef BACKEND_CANCEL_H=0A= +#define BACKEND_CANCEL_H=0A= +=0A= +#define MAX_CANCEL_MSG 128=0A= +=0A= +extern Size CancelBackendMsgShmemSize(void);=0A= +extern void BackendCancelShmemInit(void);=0A= +extern void BackendCancelInit(int backend_id);=0A= +=0A= +extern int SetBackendCancelMessage(pid_t backend, char *message);=0A= +extern bool HasCancelMessage(void);=0A= +extern int GetCancelMessage(char **msg, size_t len);=0A= +=0A= +#endif /* BACKEND_CANCEL_H */=0A= --=20=0A= 2.14.1.145.gb3622a4ee=0A= =0A= --Apple-Mail=_C90F487B-FFF7-4AC3-9D60-2535A5A63E17 Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --Apple-Mail=_C90F487B-FFF7-4AC3-9D60-2535A5A63E17--