From 30ec41aa015af53af03d13f621f04f5a2ae1c20b Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Sun, 17 Jan 2021 11:35:57 -0800
Subject: [PATCH v33 2/8] Introducing PGresultHandler abstraction

Many frontend applications in src/bin and src/bin/scripts have
functions that include the pattern:

	result = PQexec(...)
	if (PQresultStatus(result) != ...)
	{
		pg_log_{notice,warning,error} ...
		...
		exit(1)
	}

The exact error handling behavior differs from application to
application.  Factoring functions out of frontend applications into
src/fe_utils requires abstracting the error handling so that various
callers can specify different behavior consistent with their
idiosyncratic needs.

For callers without idiosyncratic needs, who simply want to do
whatever is normal and customary, a default handler is defined.
This seems cleaner than duplicating the implementation of the most
customary error handling behavior across numerous frontend
applications.  It also makes it easier for a code contributer to
know which behavior is standard, given that they would otherwise
need to survey all frontend applications trying to deduce which
behavior is most common.

Not done here, but the src/bin/scripts/scripts_parallel.[ch] have
hardcoded error handling appropriate to vacuumdb.c and reindexdb.c,
including the assumption that the parallel slots implement a command
rather than a query, and that no rows will be returned.  The
PGresultHandler abstraction will be used to make parallel slots more
flexible so that pg_amcheck (which executes queries and expects rows
to be returned) can use them also.
---
 src/fe_utils/Makefile               |  1 +
 src/fe_utils/pgreshandler.c         | 55 +++++++++++++++++++++++++++++
 src/include/fe_utils/pgreshandler.h | 27 ++++++++++++++
 src/tools/msvc/Mkvcbuild.pm         |  2 +-
 4 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 src/fe_utils/pgreshandler.c
 create mode 100644 src/include/fe_utils/pgreshandler.h

diff --git a/src/fe_utils/Makefile b/src/fe_utils/Makefile
index d6c328faf1..bc77d42dbe 100644
--- a/src/fe_utils/Makefile
+++ b/src/fe_utils/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	conditional.o \
 	exit_utils.o \
 	mbprint.o \
+	pgreshandler.o \
 	print.o \
 	psqlscan.o \
 	recovery_gen.o \
diff --git a/src/fe_utils/pgreshandler.c b/src/fe_utils/pgreshandler.c
new file mode 100644
index 0000000000..689c6770a5
--- /dev/null
+++ b/src/fe_utils/pgreshandler.c
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * Supporting routines for modular handling of PGresult error conditions.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/fe_utils/pgreshandler.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "fe_utils/exit_utils.h"
+#include "fe_utils/pgreshandler.h"
+
+/*
+ * Implements the PGresultHandler abstract interface.
+ *
+ * The pgres_default_handler can be used by applications not requiring
+ * divergent query error handling functionality.  It checks a PGresult against
+ * the supplied expectations for status and number of rows returned.  If they
+ * do not match, it logs errors and exits.  The exact error messages and log
+ * levels are chosen to precisely match the historical pg_dump behavior.
+ */
+PGresult *
+pgres_default_handler(PGresult *res, PGconn *conn,
+					  ExecStatusType expected_status, int expected_ntups,
+					  const char *query)
+{
+	if (PQresultStatus(res) != expected_status)
+	{
+		pg_log_error("query failed: %s", PQerrorMessage(conn));
+		pg_log_error("query was: %s", query);
+		PQfinish(conn);
+		exit_nicely(1);
+	}
+	if (expected_ntups >= 0)
+	{
+		int ntups = PQntuples(res);
+
+		if (ntups != expected_ntups)
+		{
+			if (expected_ntups == 1)
+				fatal(ngettext("query returned %d row instead of one: %s",
+							   "query returned %d rows instead of one: %s",
+							   ntups),
+					ntups, query);
+			fatal(ngettext("query returned %d row instead of %d: %s",
+						   "query returned %d rows instead of %d: %s",
+						   ntups),
+				ntups, expected_ntups, query);
+		}
+	}
+	return res;
+}
diff --git a/src/include/fe_utils/pgreshandler.h b/src/include/fe_utils/pgreshandler.h
new file mode 100644
index 0000000000..4cc4c02eca
--- /dev/null
+++ b/src/include/fe_utils/pgreshandler.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * Modular handling of PGresult error conditions.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/fe_utils/query_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PGRES_HANDLER_H
+#define PGRES_HANDLER_H
+
+#include "libpq-fe.h"
+
+typedef PGresult *(*PGresultHandler) (PGresult *res,
+									  PGconn *conn,
+									  ExecStatusType expected_status,
+									  int expected_ntups,
+									  const char *query);
+
+extern PGresult *pgres_default_handler(PGresult *res, PGconn *conn,
+									   ExecStatusType expected_status,
+									   int expected_ntups, const char *query);
+
+#endif							/* PGRES_HANDLER_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 0c6c1c996f..4385962c7c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -147,7 +147,7 @@ sub mkvcbuild
 	our @pgcommonbkndfiles = @pgcommonallfiles;
 
 	our @pgfeutilsfiles = qw(
-	  archive.c cancel.c conditional.c exit_utils.c mbprint.c print.c psqlscan.l
+	  archive.c cancel.c conditional.c exit_utils.c mbprint.c pgreshandler.c print.c psqlscan.l
 	  psqlscan.c simple_list.c string_utils.c recovery_gen.c);
 
 	$libpgport = $solution->AddProject('libpgport', 'lib', 'misc');
-- 
2.21.1 (Apple Git-122.3)

