diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 910e9a8..eb0c588 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -5941,12 +5941,28 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit
- Enables tracing of the client/server communication to a debugging file stream.
+ Enables tracing of the client/server communication to a debugging file
+ stream.
+ Only available when protocol version 3 or higher is used.
void PQtrace(PGconn *conn, FILE *stream);
+
+ Each line consists of: an optional timestamp, a direction indicator
+ (> for messages from client to server,
+ and < for messages from server to client),
+ message type, message length, and message contents.
+ Protocol strings are enclosed in double quotes, while strings used as data
+ values are enclosed in single quotes. Non-printable chars are printed as
+ hexadecimal escapes.
+ For numerical quantities, 16-bit values are preceded with #
+ and 32-bit values are printed without a prefix.
+ Further message-type-specific detail can be found in
+ .
+
+
On Windows, if the libpq library and an application are
@@ -5961,6 +5977,28 @@ void PQtrace(PGconn *conn, FILE *stream);
+
+ PQtraceSetFlagsPQtraceSetFlags
+
+
+
+ Controls the tracing behavior of client/server communication.
+
+void PQtraceSetFlags(PGconn *conn, int flags);
+
+
+
+
+ flags contains flag bits describing the operating mode
+ of tracing.
+ If flags contains PQTRACE_SUPPRESS_TIMESTAMPS,
+ then the timestamp is not included when printing each message.
+ This function must be called after calling PQtrace.
+
+
+
+
+
PQuntracePQuntrace
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 2aca882..2311399 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -41,6 +41,7 @@ OBJS = \
fe-secure.o \
legacy-pqsignal.o \
libpq-events.o \
+ libpq-trace.o \
pqexpbuffer.o \
fe-auth.o
@@ -113,6 +114,7 @@ install: all installdirs install-lib
$(INSTALL_DATA) $(srcdir)/libpq-fe.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-events.h '$(DESTDIR)$(includedir)'
$(INSTALL_DATA) $(srcdir)/libpq-int.h '$(DESTDIR)$(includedir_internal)'
+ $(INSTALL_DATA) $(srcdir)/libpq-trace.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pqexpbuffer.h '$(DESTDIR)$(includedir_internal)'
$(INSTALL_DATA) $(srcdir)/pg_service.conf.sample '$(DESTDIR)$(datadir)/pg_service.conf.sample'
@@ -126,6 +128,7 @@ uninstall: uninstall-lib
rm -f '$(DESTDIR)$(includedir)/libpq-fe.h'
rm -f '$(DESTDIR)$(includedir)/libpq-events.h'
rm -f '$(DESTDIR)$(includedir_internal)/libpq-int.h'
+ rm -f '$(DESTDIR)$(includedir_internal)/libpq-trace.h'
rm -f '$(DESTDIR)$(includedir_internal)/pqexpbuffer.h'
rm -f '$(DESTDIR)$(datadir)/pg_service.conf.sample'
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index bbc1f90..09f1111 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -179,3 +179,4 @@ PQgetgssctx 176
PQsetSSLKeyPassHook_OpenSSL 177
PQgetSSLKeyPassHook_OpenSSL 178
PQdefaultSSLKeyPassHook_OpenSSL 179
+PQtraceSetFlags 180
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 29054ba..c711669 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -6810,27 +6810,6 @@ PQsetErrorContextVisibility(PGconn *conn, PGContextVisibility show_context)
return old;
}
-void
-PQtrace(PGconn *conn, FILE *debug_port)
-{
- if (conn == NULL)
- return;
- PQuntrace(conn);
- conn->Pfdebug = debug_port;
-}
-
-void
-PQuntrace(PGconn *conn)
-{
- if (conn == NULL)
- return;
- if (conn->Pfdebug)
- {
- fflush(conn->Pfdebug);
- conn->Pfdebug = NULL;
- }
-}
-
PQnoticeReceiver
PQsetNoticeReceiver(PGconn *conn, PQnoticeReceiver proc, void *arg)
{
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 9a03804..5009e98 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -966,10 +966,6 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
pgParameterStatus *pstatus;
pgParameterStatus *prev;
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "pqSaveParameterStatus: '%s' = '%s'\n",
- name, value);
-
/*
* Forget any old information about the parameter
*/
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index ce2d24b..bfa730f 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -49,6 +49,7 @@
#include "libpq-fe.h"
#include "libpq-int.h"
+#include "libpq-trace.h"
#include "mb/pg_wchar.h"
#include "pg_config_paths.h"
#include "port/pg_bswap.h"
@@ -84,9 +85,6 @@ pqGetc(char *result, PGconn *conn)
*result = conn->inBuffer[conn->inCursor++];
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "From backend> %c\n", *result);
-
return 0;
}
@@ -100,9 +98,6 @@ pqPutc(char c, PGconn *conn)
if (pqPutMsgBytes(&c, 1, conn))
return EOF;
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "To backend> %c\n", c);
-
return 0;
}
@@ -138,10 +133,6 @@ pqGets_internal(PQExpBuffer buf, PGconn *conn, bool resetbuffer)
conn->inCursor = ++inCursor;
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "From backend> \"%s\"\n",
- buf->data);
-
return 0;
}
@@ -167,9 +158,6 @@ pqPuts(const char *s, PGconn *conn)
if (pqPutMsgBytes(s, strlen(s) + 1, conn))
return EOF;
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "To backend> \"%s\"\n", s);
-
return 0;
}
@@ -188,13 +176,6 @@ pqGetnchar(char *s, size_t len, PGconn *conn)
conn->inCursor += len;
- if (conn->Pfdebug)
- {
- fprintf(conn->Pfdebug, "From backend (%lu)> ", (unsigned long) len);
- fwrite(s, 1, len, conn->Pfdebug);
- fprintf(conn->Pfdebug, "\n");
- }
-
return 0;
}
@@ -212,13 +193,6 @@ pqSkipnchar(size_t len, PGconn *conn)
if (len > (size_t) (conn->inEnd - conn->inCursor))
return EOF;
- if (conn->Pfdebug)
- {
- fprintf(conn->Pfdebug, "From backend (%lu)> ", (unsigned long) len);
- fwrite(conn->inBuffer + conn->inCursor, 1, len, conn->Pfdebug);
- fprintf(conn->Pfdebug, "\n");
- }
-
conn->inCursor += len;
return 0;
@@ -234,13 +208,6 @@ pqPutnchar(const char *s, size_t len, PGconn *conn)
if (pqPutMsgBytes(s, len, conn))
return EOF;
- if (conn->Pfdebug)
- {
- fprintf(conn->Pfdebug, "To backend> ");
- fwrite(s, 1, len, conn->Pfdebug);
- fprintf(conn->Pfdebug, "\n");
- }
-
return 0;
}
@@ -278,9 +245,6 @@ pqGetInt(int *result, size_t bytes, PGconn *conn)
return EOF;
}
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "From backend (#%lu)> %d\n", (unsigned long) bytes, *result);
-
return 0;
}
@@ -314,9 +278,6 @@ pqPutInt(int value, size_t bytes, PGconn *conn)
return EOF;
}
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "To backend (%lu#)> %d\n", (unsigned long) bytes, value);
-
return 0;
}
@@ -525,10 +486,6 @@ pqPutMsgStart(char msg_type, PGconn *conn)
conn->outMsgEnd = endPos;
/* length word, if needed, will be filled in by pqPutMsgEnd */
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "To backend> Msg %c\n",
- msg_type ? msg_type : ' ');
-
return 0;
}
@@ -563,10 +520,6 @@ pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
int
pqPutMsgEnd(PGconn *conn)
{
- if (conn->Pfdebug)
- fprintf(conn->Pfdebug, "To backend> Msg complete, length %u\n",
- conn->outMsgEnd - conn->outCount);
-
/* Fill in length word if needed */
if (conn->outMsgStart >= 0)
{
@@ -576,6 +529,10 @@ pqPutMsgEnd(PGconn *conn)
memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4);
}
+ if(conn->Pfdebug)
+ pqTraceOutputMsg(conn, conn->outMsgStart, MSGDIR_FROM_FRONTEND);
+
+
/* Make message eligible to send */
conn->outCount = conn->outMsgEnd;
@@ -1002,11 +959,13 @@ pqSendSome(PGconn *conn, int len)
int
pqFlush(PGconn *conn)
{
- if (conn->Pfdebug)
- fflush(conn->Pfdebug);
-
if (conn->outCount > 0)
+ {
+ if (conn->Pfdebug)
+ fflush(conn->Pfdebug);
+
return pqSendSome(conn, conn->outCount);
+ }
return 0;
}
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 2ca8c05..07b4b3b 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -28,6 +28,7 @@
#include "libpq-fe.h"
#include "libpq-int.h"
+#include "libpq-trace.h"
#include "mb/pg_wchar.h"
#include "port/pg_bswap.h"
@@ -421,6 +422,9 @@ pqParseInput3(PGconn *conn)
{
/* Normal case: parsing agrees with specified length */
conn->inStart = conn->inCursor;
+
+ if(conn->Pfdebug)
+ pqTraceOutputMsg(conn, conn->inStart - 5 - msgLength, MSGDIR_FROM_BACKEND);
}
else
{
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index fa9b62a..59ea464 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -362,7 +362,9 @@ extern PGContextVisibility PQsetErrorContextVisibility(PGconn *conn,
PGContextVisibility show_context);
/* Enable/disable tracing */
+#define PQTRACE_SUPPRESS_TIMESTAMPS 1
extern void PQtrace(PGconn *conn, FILE *debug_port);
+extern void PQtraceSetFlags(PGconn *conn, int flags);
extern void PQuntrace(PGconn *conn);
/* Override default notice handling routines */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index adf149a..e35ad19 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -376,6 +376,7 @@ struct pg_conn
/* Optional file to write trace info to */
FILE *Pfdebug;
+ int traceFlags;
/* Callback procedures for notice message processing */
PGNoticeHooks noticeHooks;
diff --git a/src/interfaces/libpq/libpq-trace.c b/src/interfaces/libpq/libpq-trace.c
new file mode 100644
index 0000000..ef294fa
--- /dev/null
+++ b/src/interfaces/libpq/libpq-trace.c
@@ -0,0 +1,689 @@
+/*-------------------------------------------------------------------------
+ *
+ * libpq-trace.c
+ * functions for libpq protocol tracing
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/libpq-trace.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include
+#include
+
+#ifdef WIN32
+#include "win32.h"
+#else
+#include
+#include
+#endif
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "libpq-trace.h"
+#include "pgtime.h"
+#include "port/pg_bswap.h"
+
+/*
+ * protocol message types:
+ *
+ * protocol_message_type_b[]: message types sent by a backend
+ * protocol_message_type_f[]: message types sent by a frontend
+ */
+static const char *const protocol_message_type_b[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x00 ... \x0f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x10 ... \x1f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x20 ... \x2f */
+ 0, /* 0 */
+ "ParseComplete", /* 1 */
+ "BindComplete", /* 2 */
+ "CloseComplete", /* 3 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x34 ... \x3f */
+ 0, /* @ */
+ "NotificationResponse", /* A */
+ 0, /* B */
+ "CommandComplete", /* C */
+ "DataRow", /* D */
+ "ErrorResponse", /* E */
+ 0, /* F */
+ "CopyInResponse", /* G */
+ "CopyOutResponse", /* H */
+ "EmptyQueryResponse", /* I */
+ 0, /* J */
+ "BackendKeyData", /* K */
+ 0, /* L */
+ 0, /* M */
+ "NoticeResponse", /* N */
+ 0, /* O */
+ 0, /* P */
+ 0, /* Q */
+ "Authentication", /* R */
+ "ParameterStatus", /* S */
+ "RowDescription", /* T */
+ 0, /* U */
+ "FunctionCallResponse", /* V */
+ "CopyBothResponse", /* W */
+ 0, /* X */
+ 0, /* Y */
+ "ReadyForQuery", /* Z */
+ 0, 0, 0, 0, 0, /* \x5b ... \x5f */
+ 0, /* ` */
+ 0, /* a */
+ 0, /* b */
+ "CopyDone", /* c */
+ "CopyData", /* d */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x65 ... \0x6d */
+ "NoData", /* n */
+ 0, /* o */
+ 0, /* p */
+ 0, /* q */
+ 0, /* r */
+ "PortalSuspended", /* s */
+ "ParameterDescription", /* t */
+ 0, /* u */
+ "NegotiateProtocolVersion", /* v */
+};
+
+static const char *const protocol_message_type_f[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x00 ... \x0f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x10 ... \x1f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x20 ... \x2f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* \x30 ... \x3f */
+ 0, /* @ */
+ 0, /* A */
+ "Bind", /* B */
+ "Close", /* C */
+ "Describe", /* D */
+ "Execute", /* E */
+ "FunctionCall", /* F */
+ 0, /* G */
+ "Flush", /* H */
+ 0, 0, 0, 0, 0, 0, 0, /* I ... O */
+ "Parse", /* P */
+ "Query", /* Q */
+ 0, /* R */
+ "Sync", /* S */
+ 0, 0, 0, 0, /* T ... W */
+ "Terminate", /* X */
+ 0, 0, 0, 0, 0, 0, 0, /* \x59 ... \x5f */
+ 0, /* ` */
+ 0, /* a */
+ 0, /* b */
+ "CopyDone", /* c */
+ "CopyData", /* d */
+ 0, /* e */
+ "CopyFail", /* f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, /* g ... o */
+ "AuthenticationResponse", /* p */
+};
+
+#define FORMATTED_TS_LEN 128 /* formatted timestamp length */
+
+static const char *pqGetProtocolMsgType(unsigned char c,
+ PGCommSource commsource);
+static void pqTraceOutputByte1(int LogEnd, int *cursor, FILE *pfdebug, char v);
+static int pqTraceOutputInt(char *buf, int LogEnd,int *cursor,
+ FILE *pfdebug, int length);
+static int pqTraceOutputString(char *buf, int LogEnd,int *cursor, FILE *pfdebug);
+static void pqTraceOutputBinary(char *v, int length, FILE *pfdebug);
+static void pqTraceOutputNchar(char *buf, int LogEnd, int *cursor,
+ FILE *pfdebug, int len);
+
+/* -------------------------
+ * FE/BE trace support
+ *
+ * We accumulate frontend message pieces in an array as the libpq code writes
+ * them, and log the complete message when pqTraceOutputFeMsg is called.
+ * For backend, we print the pieces as soon as we receive them from the server.
+ * -------------------------
+ */
+
+void
+PQtrace(PGconn *conn, FILE *debug_port)
+{
+ if (conn == NULL)
+ return;
+ /* Protocol 2.0 does not support tracing. */
+ if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
+ return;
+ PQuntrace(conn);
+ if (debug_port == NULL)
+ return;
+
+ setvbuf(debug_port, NULL, _IOLBF, 0);
+ conn->Pfdebug = debug_port;
+ conn->traceFlags = 0;
+}
+
+void
+PQuntrace(PGconn *conn)
+{
+ if (conn == NULL)
+ return;
+ if (conn->Pfdebug)
+ {
+ fflush(conn->Pfdebug);
+ conn->Pfdebug = NULL;
+ }
+
+ conn->traceFlags = 0;
+}
+
+void
+PQtraceSetFlags(PGconn *conn, int flags)
+{
+ if (conn == NULL)
+ return;
+ /* Protocol 2.0 is not supported. */
+ if (PG_PROTOCOL_MAJOR(conn->pversion) < 3)
+ return;
+ /* If PQtrace() failed, do nothing. */
+ if (conn->Pfdebug == NULL)
+ return;
+ conn->traceFlags = flags;
+}
+
+/*
+ * pqGetProtocolMsgType:
+ * Get a protocol type from first byte identifier
+ */
+static const char *
+pqGetProtocolMsgType(unsigned char c, PGCommSource commsource)
+{
+ if (commsource == MSGDIR_FROM_BACKEND &&
+ c < lengthof(protocol_message_type_b) &&
+ protocol_message_type_b[c] != NULL)
+ return protocol_message_type_b[c];
+
+ if (commsource == MSGDIR_FROM_FRONTEND &&
+ c < lengthof(protocol_message_type_f) &&
+ protocol_message_type_f[c] != NULL)
+ return protocol_message_type_f[c];
+
+ return "UnknownMessage:";
+}
+
+/*
+ * Print the current time, with microseconds, into a caller-supplied
+ * buffer.
+ * Cribbed from setup_formatted_log_time, but much simpler.
+ */
+static void
+pqTraceFormatTimestamp(char *timestr, size_t ts_len)
+{
+ struct timeval tval;
+ pg_time_t stamp_time;
+
+ gettimeofday(&tval, NULL);
+ stamp_time = (pg_time_t) tval.tv_sec;
+
+ strftime(timestr, ts_len,
+ "%Y-%m-%d %H:%M:%S",
+ localtime(&stamp_time));
+ /* append microseconds */
+ sprintf(timestr + strlen(timestr), ".%06d", (int) (tval.tv_usec));
+}
+
+/*
+ * pqTraceOutputByte1: output 1 char message to the log
+ */
+static void
+pqTraceOutputByte1(int LogEnd, int *cursor, FILE *pfdebug, char v)
+{
+ if (cursor == NULL)
+ return;
+ if (*cursor > LogEnd)
+ return;
+
+ /*
+ * Show non-printable data in hex format, including the
+ * terminating \0 that completes ErrorResponse and NoticeResponse
+ * messages.
+ */
+ if (!isprint(v))
+ fprintf(pfdebug, "\\x%02x", v);
+ else
+ fprintf(pfdebug, "%c", v);
+
+ if (*cursor < LogEnd)
+ fprintf(pfdebug, " ");
+ else
+ fprintf(pfdebug, "\n");
+}
+
+/*
+ * pqTraceOutputInt: output a 2- or 4-byte integer message to the log
+ */
+static int
+pqTraceOutputInt(char *buf, int LogEnd,int *cursor, FILE *pfdebug, int length)
+{
+ char *prefix = length == 4 ? "" : "#";
+ uint16 tmp2;
+ uint32 tmp4;
+ int result;
+
+
+ if (*cursor + length > LogEnd)
+ return EOF;
+
+ switch(length)
+ {
+ case 2:
+ memcpy(&tmp2, buf + *cursor , 2);
+ result = (int) pg_ntoh16(tmp2);
+ *cursor += 2;
+ break;
+ case 4:
+ memcpy(&tmp4, buf + *cursor , 4);
+ result = (int) pg_ntoh32(tmp4);
+ *cursor += 4;
+ break;
+ default:
+ fprintf(pfdebug,
+ "integer of size %d not supported by pqTraceOutputInt", length);
+ return EOF;
+ }
+ fprintf(pfdebug, "%s%d", prefix, result);
+
+ if (*cursor < LogEnd)
+ fprintf(pfdebug, " ");
+ else
+ fprintf(pfdebug, "\n");
+
+ return (int) result;
+}
+
+/*
+ * pqTraceOutputString: output a null-terminated string to the log
+ */
+static int
+pqTraceOutputString(char *buf, int LogEnd,int *cursor, FILE *pfdebug)
+{
+ int csr = *cursor;
+ int slen;
+ char *s;
+
+ while (csr < LogEnd && buf[csr])
+ csr++;
+
+ if (csr >= LogEnd)
+ return EOF;
+
+ slen = csr - *cursor;
+
+ s = (char *) malloc(sizeof(char) * (slen + 1));
+ memcpy(s, buf + *cursor, slen);
+ s[slen] = '\0';
+
+ fprintf(pfdebug, "\"%s\"", s);
+
+ *cursor = ++csr;
+
+ if (*cursor < LogEnd)
+ fprintf(pfdebug, " ");
+ else
+ fprintf(pfdebug, "\n");
+
+ return 0;
+}
+
+/*
+ * pqTraceOutputBinary: output a string possibly consisting of
+ * non-printable characters. Hex representation is used for such
+ * chars; others are printed normally.
+ *
+ * XXX this probably doesn't do a great job with multibyte chars, but then we
+ * don't know what is text and what encoding it'd be in.
+ */
+static void
+pqTraceOutputBinary(char *v, int length, FILE *pfdebug)
+{
+ int i,
+ next; /* first char not yet printed */
+
+ for (next = i = 0; i < length; ++i)
+ {
+ if (isprint(v[i]))
+ continue;
+ else
+ {
+ fwrite(v + next, 1, i - next, pfdebug);
+ fprintf(pfdebug, "\\x%02x", v[i]);
+ next = i + 1;
+ }
+ }
+ if (next < length)
+ fwrite(v + next, 1, length - next, pfdebug);
+}
+
+/*
+ * pqTraceOutputNchar: output a string of exactly len bytes message to the log
+ */
+static void
+pqTraceOutputNchar(char *buf, int LogEnd, int *cursor, FILE *pfdebug, int len)
+{
+ char *v = '\0';
+
+ memcpy(v, buf + *cursor, len);
+ *cursor += len;
+
+ fprintf(pfdebug, "\'");
+ pqTraceOutputBinary(v, len, pfdebug);
+ fprintf(pfdebug, "\'");
+
+ if (*cursor < LogEnd)
+ fprintf(pfdebug, " ");
+ else
+ fprintf(pfdebug, "\n");
+}
+
+int
+pqTraceOutputMsg(PGconn *conn, int msgCursor, PGCommSource commsource)
+{
+ char timestr[FORMATTED_TS_LEN];
+ char id = '\0';
+ uint32 length32;
+ int length;
+ char *prefix = commsource == MSGDIR_FROM_BACKEND ? "<" : ">";
+ int LogCount = conn->outCount;
+ int LogEnd = commsource == MSGDIR_FROM_BACKEND ? conn->inCursor : conn->outMsgEnd;
+ int LogCursor = msgCursor;
+ char *logBuffer = commsource == MSGDIR_FROM_BACKEND ? conn->inBuffer : conn->outBuffer;
+ const char *message_type = "UnkownMessage";
+
+ if ((conn->traceFlags & PQTRACE_SUPPRESS_TIMESTAMPS) == 0)
+ pqTraceFormatTimestamp(timestr, sizeof(timestr));
+ else
+ timestr[0] = '\0';
+
+ if (commsource == MSGDIR_FROM_BACKEND)
+ id = logBuffer[LogCursor++];
+ else
+ {
+ if (LogCursor > LogCount)
+ id = logBuffer[LogCount];
+ }
+
+ if (LogCursor + 4 > LogEnd)
+ return EOF;
+ memcpy(&length32, logBuffer + LogCursor , 4);
+ length = (int) pg_ntoh32(length32);
+ LogCursor += 4;
+
+ if (id == '\0')
+ {
+ switch(length)
+ {
+ case 8:
+ {
+ uint32 result32;
+ int result;
+ memcpy(&result32, logBuffer + LogCursor, 4);
+ result = (int) pg_ntoh32(result32);
+ if (result == NEGOTIATE_SSL_CODE)
+ message_type = "SSLRequest";
+ else if (result == NEGOTIATE_GSS_CODE)
+ message_type = "GSSRequest";
+ break;
+ }
+ case 16:
+ message_type = "CancelRequest";
+ break;
+ default :
+ message_type = "StartupMessage";
+ break;
+ }
+ }
+ else
+ message_type =
+ pqGetProtocolMsgType(id, commsource);
+
+ fprintf(conn->Pfdebug, "%s\t%s\t%s\t%d ", timestr, prefix, message_type, length);
+
+ switch(id)
+ {
+ case 'R': /* Authentication */
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ break;
+ case 'K': /* secret key data from the backend */
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ break;
+ case 'B': /* Bind */
+ {
+ int nparams;
+ int nbytes;
+ int i;
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ nparams = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 2);
+ for (i = 0; i < nparams; i++)
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 2);
+ nparams = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 2);
+ for (i = 0; i < nparams; i++)
+ {
+ nbytes = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 4);
+ pqTraceOutputNchar(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, nbytes);
+ }
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ break;
+ }
+ case 'C': /* Close(F) or Command Complete(B) */
+ if (commsource == MSGDIR_FROM_BACKEND)
+ fprintf(conn->Pfdebug, "\"%s\"\n", logBuffer + LogCursor);
+ else
+ {
+ pqTraceOutputByte1(LogEnd, &LogCursor, conn->Pfdebug,
+ logBuffer[LogCursor++]);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ }
+ break;
+ case 'd': /* Copy Data */
+ /* Drop COPY data to reduce the overhead of logging. */
+ fprintf(conn->Pfdebug, "\n");
+ break;
+ case 'f': /* Copy Fail */
+ fprintf(conn->Pfdebug, "\"%s\"\n", logBuffer + LogCursor);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ break;
+ case 'G': /* Start Copy In */
+ pqTraceOutputByte1(LogEnd, &LogCursor,
+ conn->Pfdebug, logBuffer[LogCursor++]);
+ while (LogEnd > LogCursor)
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ break;
+ case 'H': /* Flush(F) or Start Copy Out(B) */
+ if (commsource == MSGDIR_FROM_BACKEND)
+ {
+ pqTraceOutputByte1(LogEnd, &LogCursor,
+ conn->Pfdebug, logBuffer[LogCursor++]);
+ while (LogEnd > LogCursor)
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ }
+ else
+ fprintf(conn->Pfdebug, "\n");
+ break;
+ case 'W': /* Start Copy Both */
+ pqTraceOutputByte1(LogEnd, &LogCursor,
+ conn->Pfdebug, logBuffer[LogCursor++]);
+ while (LogEnd > LogCursor)
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ break;
+ case 'D': /* Describe(F) or Data Row(B) */
+ if (commsource == MSGDIR_FROM_BACKEND)
+ {
+ int len;
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ while (LogEnd > LogCursor)
+ {
+ len = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 4);
+ pqTraceOutputNchar(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, len);
+ }
+ }
+ else
+ {
+ pqTraceOutputByte1(LogEnd, &LogCursor, conn->Pfdebug,
+ logBuffer[LogCursor++]);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ }
+ break;
+ case 'E': /* Execute(F) or Error Return(B) */
+ if (commsource == MSGDIR_FROM_BACKEND)
+ {
+ while (LogEnd > LogCursor)
+ {
+ pqTraceOutputByte1(LogEnd, &LogCursor, conn->Pfdebug,
+ logBuffer[LogCursor++]);
+ if (logBuffer[LogCursor] == '\0')
+ break;
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ }
+ }
+ else
+ {
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ }
+ break;
+ case 'F': /* Function Call */
+ {
+ int nargs;
+ int nbytes;
+ int i;
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ nargs = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 2);
+ for (i = 0; i < nargs; i++)
+ {
+ nbytes = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 4);
+ pqTraceOutputNchar(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, nbytes);
+ }
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ break;
+ }
+ case 'V': /* Function Call response */
+ {
+ int len;
+ len = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputNchar(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, len);
+ break;
+ }
+ case 'v': /* Negotiate Protocol Version */
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ break;
+ case 'N': /* Notice Response */
+ while (LogEnd > LogCursor)
+ {
+ pqTraceOutputByte1(LogEnd, &LogCursor, conn->Pfdebug,
+ logBuffer[LogCursor++]);
+ if (logBuffer[LogCursor] == '\0')
+ break;
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ }
+ break;
+ case 'A': /* Notification Response */
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ break;
+ case 't': /* Parameter Description */
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ while (LogEnd > LogCursor)
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ break;
+ case 'S': /* Parameter Status(B) or Sync(F) */
+ if (commsource == MSGDIR_FROM_BACKEND)
+ {
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ }
+ else
+ fprintf(conn->Pfdebug, "\n");
+ break;
+ case 'P': /* Parse */
+ {
+ int nparams;
+ int i;
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ nparams = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor,
+ conn->Pfdebug, 2);
+ for (i = 0; i < nparams; i++)
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ break;
+ }
+ case 'Q': /* Query */
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ break;
+ case 'Z': /* Ready For Query */
+ pqTraceOutputByte1(LogEnd, &LogCursor, conn->Pfdebug,
+ logBuffer[LogCursor++]);
+ break;
+ case 'T': /* Row Description */
+ {
+ int nfields;
+ int i;
+ nfields = pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ for (i = 0; i < nfields; i++)
+ {
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 2);
+ }
+ break;
+ }
+ case '2': /* Bind Complete */
+ case '3': /* Close Complete */
+ case 'c': /* Copy Done */
+ case 'I': /* empty query */
+ case 'n': /* No Data */
+ case '1': /* Parse Complete */
+ case 's': /* Portal Suspended */
+ case 'X': /* Terminate */
+ /* No message content */
+ fprintf(conn->Pfdebug, "\n");
+ break;
+ case '\0' :
+ /* CancelRequest SSLRequest. GSSENCRequest and StartupMessage */
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ if (length == 8)
+ {
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ pqTraceOutputInt(logBuffer, LogEnd, &LogCursor, conn->Pfdebug, 4);
+ }
+ else
+ {
+ while (LogEnd > LogCursor)
+ pqTraceOutputString(logBuffer, LogEnd, &LogCursor,conn->Pfdebug);
+ }
+ break;
+ default:
+ fprintf(conn->Pfdebug, "Invalid Protocol:%c\n", id);
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/interfaces/libpq/libpq-trace.h b/src/interfaces/libpq/libpq-trace.h
new file mode 100644
index 0000000..71b66da
--- /dev/null
+++ b/src/interfaces/libpq/libpq-trace.h
@@ -0,0 +1,39 @@
+/*-------------------------------------------------------------------------
+ *
+ * libpq-trace.h
+ * This file contains definitions for structures and
+ * externs for functions used by libpq protocol tracing.
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq/libpq-trace.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef LIBPQ_TRACE_H
+#define LIBPQ_TRACE_H
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Log message source */
+typedef enum
+{
+ MSGDIR_FROM_BACKEND,
+ MSGDIR_FROM_FRONTEND
+} PGCommSource;
+
+extern int pqTraceOutputMsg(PGconn *conn, int msgCursor, PGCommSource commsource);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBPQ_TRACE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8bd95ae..18a692d 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1523,6 +1523,7 @@ PGAlignedXLogBlock
PGAsyncStatusType
PGCALL2
PGChecksummablePage
+PGCommSource
PGContextVisibility
PGEvent
PGEventConnDestroy