On 23.01.22 18:17, Fabien COELHO wrote:
But I think if we are adding a libpq API function to work around a
misbehavior in libpq, we might as well fix the misbehavior in libpq to
begin with. Adding a new public libpq function is a significant step,
needs documentation, etc.
I'm not so sure.
The choice is (1) change the behavior of an existing function or (2) add
a new function. Whatever the existing function does, the usual anwer to
API changes is "someone is going to complain because it breaks their
code", so "Returned with feedback", hence I did not even try. The
advantage of (2) is that it does not harm anyone to have a new function
that they just do not need to use.
It would be better to do without. Also, it makes one wonder how
others are supposed to use this multiple-results API properly, if even
psql can't do it without extending libpq. Needs more thought.
Fine with me! Obviously I'm okay if libpq is repaired instead of writing
strange code on the client to deal with strange behavior.
I have a new thought on this, as long as we are looking into libpq. Why
can't libpq provide a variant of PQexec() that returns all results,
instead of just the last one. It has all the information, all it has to
do is return the results instead of throwing them away. Then the
changes in psql would be very limited, and we don't have to re-invent
PQexec() from its pieces in psql. And this would also make it easier
for other clients and user code to make use of this functionality more
easily.
Attached is a rough draft of what this could look like. It basically
works. Thoughts?From 947d9d98507d6c93b547ac17fef41c1870c0d577 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Thu, 27 Jan 2022 14:09:52 +0100
Subject: [PATCH] WIP: PQexecMulti
---
src/bin/psql/common.c | 44 +++++++++++++++++++-------------
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-exec.c | 37 +++++++++++++++++++++++++++
src/interfaces/libpq/libpq-fe.h | 1 +
4 files changed, 65 insertions(+), 18 deletions(-)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 3503605a7d..deaaa54f10 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1195,7 +1195,6 @@ bool
SendQuery(const char *query)
{
bool timing = pset.timing;
- PGresult *results;
PGTransactionStatusType transaction_status;
double elapsed_msec = 0;
bool OK = false;
@@ -1247,15 +1246,17 @@ SendQuery(const char *query)
!pset.autocommit &&
!command_no_begin(query))
{
- results = PQexec(pset.db, "BEGIN");
- if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ PGresult *result;
+
+ result = PQexec(pset.db, "BEGIN");
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
{
pg_log_info("%s", PQerrorMessage(pset.db));
- ClearOrSaveResult(results);
+ ClearOrSaveResult(result);
ResetCancelConn();
goto sendquery_cleanup;
}
- ClearOrSaveResult(results);
+ ClearOrSaveResult(result);
transaction_status = PQtransactionStatus(pset.db);
}
@@ -1264,15 +1265,17 @@ SendQuery(const char *query)
(pset.cur_cmd_interactive ||
pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON))
{
- results = PQexec(pset.db, "SAVEPOINT
pg_psql_temporary_savepoint");
- if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ PGresult *result;
+
+ result = PQexec(pset.db, "SAVEPOINT
pg_psql_temporary_savepoint");
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
{
pg_log_info("%s", PQerrorMessage(pset.db));
- ClearOrSaveResult(results);
+ ClearOrSaveResult(result);
ResetCancelConn();
goto sendquery_cleanup;
}
- ClearOrSaveResult(results);
+ ClearOrSaveResult(result);
on_error_rollback_savepoint = true;
}
@@ -1281,7 +1284,6 @@ SendQuery(const char *query)
/* Describe query's result columns, without executing it */
OK = DescribeQuery(query, &elapsed_msec);
ResetCancelConn();
- results = NULL; /* PQclear(NULL) does nothing */
}
else if (pset.fetch_count <= 0 || pset.gexec_flag ||
pset.crosstab_flag || !is_select_command(query))
@@ -1289,15 +1291,19 @@ SendQuery(const char *query)
/* Default fetch-it-all-and-print mode */
instr_time before,
after;
+ PGresult **results;
+ PGresult **res;
if (timing)
INSTR_TIME_SET_CURRENT(before);
- results = PQexec(pset.db, query);
+ results = PQexecMulti(pset.db, query);
/* these operations are included in the timing result: */
ResetCancelConn();
- OK = ProcessResult(&results);
+ OK = false;
+ for (res = results; *res; res++)
+ OK |= ProcessResult(res);
if (timing)
{
@@ -1307,15 +1313,18 @@ SendQuery(const char *query)
}
/* but printing results isn't: */
- if (OK && results)
- OK = PrintQueryResults(results);
+ if (OK)
+ for (res = results; *res; res++)
+ OK |= PrintQueryResults(*res);
+
+ for (res = results; *res; res++)
+ ClearOrSaveResult(*res);
}
else
{
/* Fetch-in-segments mode */
OK = ExecQueryUsingCursor(query, &elapsed_msec);
ResetCancelConn();
- results = NULL; /* PQclear(NULL) does nothing */
}
if (!OK && pset.echo == PSQL_ECHO_ERRORS)
@@ -1341,6 +1350,7 @@ SendQuery(const char *query)
case PQTRANS_INTRANS:
+#if FIXME
/*
* Do nothing if they are messing with
savepoints themselves:
* If the user did COMMIT AND CHAIN, RELEASE or
ROLLBACK, our
@@ -1354,6 +1364,7 @@ SendQuery(const char *query)
strcmp(PQcmdStatus(results),
"ROLLBACK") == 0))
svptcmd = NULL;
else
+#endif
svptcmd = "RELEASE
pg_psql_temporary_savepoint";
break;
@@ -1379,7 +1390,6 @@ SendQuery(const char *query)
ClearOrSaveResult(svptres);
OK = false;
- PQclear(results);
ResetCancelConn();
goto sendquery_cleanup;
}
@@ -1387,8 +1397,6 @@ SendQuery(const char *query)
}
}
- ClearOrSaveResult(results);
-
/* Possible microtiming output */
if (timing)
PrintTiming(elapsed_msec);
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index e8bcc88370..878e430b92 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -186,3 +186,4 @@ PQpipelineStatus 183
PQsetTraceFlags 184
PQmblenBounded 185
PQsendFlushRequest 186
+PQexecMulti 187
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 59121873d2..90f00ad401 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -70,6 +70,7 @@ static void parseInput(PGconn *conn);
static PGresult *getCopyResult(PGconn *conn, ExecStatusType copytype);
static bool PQexecStart(PGconn *conn);
static PGresult *PQexecFinish(PGconn *conn);
+static PGresult **PQexecFinishMulti(PGconn *conn);
static int PQsendDescribe(PGconn *conn, char desc_type,
const char *desc_target);
static int check_field_number(const PGresult *res, int field_num);
@@ -2199,6 +2200,16 @@ PQexec(PGconn *conn, const char *query)
return PQexecFinish(conn);
}
+PGresult **
+PQexecMulti(PGconn *conn, const char *query)
+{
+ if (!PQexecStart(conn))
+ return NULL;
+ if (!PQsendQuery(conn, query))
+ return NULL;
+ return PQexecFinishMulti(conn);
+}
+
/*
* PQexecParams
* Like PQexec, but use extended query protocol so we can pass
parameters
@@ -2369,6 +2380,32 @@ PQexecFinish(PGconn *conn)
return lastResult;
}
+static PGresult **
+PQexecFinishMulti(PGconn *conn)
+{
+ int count = 0;
+ PGresult *result;
+ PGresult **ret = NULL;
+
+ while ((result = PQgetResult(conn)) != NULL)
+ {
+ count++;
+ ret = realloc(ret, count * sizeof(PGresult*));
+ ret[count - 1] = result;
+
+ if (result->resultStatus == PGRES_COPY_IN ||
+ result->resultStatus == PGRES_COPY_OUT ||
+ result->resultStatus == PGRES_COPY_BOTH ||
+ conn->status == CONNECTION_BAD)
+ break;
+ }
+
+ ret = realloc(ret, (count + 1) * sizeof(PGresult*));
+ ret[count] = NULL;
+
+ return ret;
+}
+
/*
* PQdescribePrepared
* Obtain information about a previously prepared statement
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 20eb855abc..61b3e9bf21 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -418,6 +418,7 @@ extern void PQsetTraceFlags(PGconn *conn, int flags);
/* Simple synchronous query */
extern PGresult *PQexec(PGconn *conn, const char *query);
+extern PGresult **PQexecMulti(PGconn *conn, const char *query);
extern PGresult *PQexecParams(PGconn *conn,
const char *command,
int nParams,
--
2.34.1