Skip to content

Commit 98811b1

Browse files
committed
pgsql: decide the NoData of a refused DISCARD ALL by the connection
A DISCARD ALL in a transaction is forwarded to the backend. On a libpq connection the Describe folded into the Execute is not sent on, so ProxySQL sends the NoData itself. That was decided by the global pgsql-use_native_backend_protocol setting. A pooled connection keeps the protocol it was opened with, so after the setting changed the NoData was missing or doubled. It is now decided by the connection the statement goes to. pgsql-discard_all_txn_block-t gets a check for this: a backend opened under the other protocol, the setting flipped without flushing the pool, then an extended DISCARD ALL in a transaction compared with direct PostgreSQL. It fails without this change.
1 parent 2ff7da3 commit 98811b1

2 files changed

Lines changed: 44 additions & 5 deletions

File tree

‎lib/PgSQL_Session.cpp‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5261,9 +5261,12 @@ PgSQL_Local_Reply PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SL
52615261
// transaction on its own and keeps the session, as it does for any error it raises, so the
52625262
// statement goes to the backend and nothing is dropped here.
52635263
if (explicit_txn) {
5264-
// On the libpq path the Describe folded into this Execute is not sent on, so the NoData
5265-
// it is owed is sent here, as when the refusal is local; a native batch carries it.
5266-
if (owes_no_data && !pgsql_thread___use_native_backend_protocol) {
5264+
// On a libpq connection the Describe folded into this Execute is not sent on, so the NoData
5265+
// it is owed is sent here, as when the refusal is local; a native batch carries it. Decided
5266+
// by the connection, not the global setting: a pooled connection keeps the protocol it was
5267+
// opened with.
5268+
PgSQL_Connection* held = (mybe && mybe->server_myds) ? mybe->server_myds->myconn : nullptr;
5269+
if (owes_no_data && (held == nullptr || held->native_mode == false)) {
52675270
client_myds->myprot.generate_no_data_packet(true);
52685271
}
52695272
return PgSQL_Local_Reply::NONE;

‎test/tap/tests/pgsql-discard_all_txn_block-t.cpp‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -829,14 +829,20 @@ static void checkBatchEndThenDiscard(PGconn* admin, const char* end_cmd, bool lo
829829

830830
static const int BATCH_END_CHECKS = 4;
831831

832+
// A pooled backend keeps the protocol it was opened with, but the NoData a libpq backend owes a
833+
// folded Describe is decided by the connection, not the global setting (the check below).
834+
static const int MIXED_POOL_CHECKS = 1;
835+
836+
static void sc_warm_pool(PgConnection& c) { c.execute("SELECT 1"); }
837+
832838
// ------------------------------------------------------------------ main
833839

834840
static const char* MODE_NAME[2] = { "libpq backend protocol", "native backend protocol" };
835841

836842
int main(int, char**) {
837843
// Existing parity cases plus 5 assertions for each DISCARD/recovery shape.
838844
plan((int)((sizeof(PARITY_CASES) / sizeof(PARITY_CASES[0]) + 2 + 5 * 2 * 2 + STALE_CHECKS +
839-
BATCH_END_CHECKS) * 2));
845+
BATCH_END_CHECKS + MIXED_POOL_CHECKS) * 2));
840846

841847
if (cl.getEnv()) return exit_status();
842848

@@ -878,7 +884,7 @@ int main(int, char**) {
878884
const bool native = (mode == 1);
879885
if (native && !native_supported) {
880886
skip((int)(sizeof(PARITY_CASES) / sizeof(PARITY_CASES[0]) + 2 + 5 * 2 * 2 + STALE_CHECKS +
881-
BATCH_END_CHECKS),
887+
BATCH_END_CHECKS + MIXED_POOL_CHECKS),
882888
"native backend protocol is unavailable in v3.0");
883889
continue;
884890
}
@@ -896,6 +902,36 @@ int main(int, char**) {
896902
checkAbortedRecovery(extended, recovery, MODE_NAME[mode]);
897903
}
898904

905+
// ---- mixed pool: a connection opened under the other protocol, then the setting flipped ----
906+
if (!native_supported) {
907+
skip(MIXED_POOL_CHECKS, "mixed pool needs both backend protocols");
908+
} else {
909+
if (!setNativeMode(admin, !native) || !flushBackendPool(admin, BACKEND_HG, saved_servers)) {
910+
cleanup();
911+
BAIL_OUT("cannot set up the mixed pool");
912+
}
913+
std::string warm_err;
914+
runLeg(true, sc_warm_pool, 1, warm_err); // the pooled backend opens under !native
915+
usleep(300000);
916+
if (!setNativeMode(admin, native)) { cleanup(); BAIL_OUT("cannot flip the protocol"); }
917+
std::string oerr, perr;
918+
std::vector<Msg> oracle = runLeg(false, sc_txn_extended, 3, oerr);
919+
std::vector<Msg> proxy = runLeg(true, sc_txn_extended, 3, perr);
920+
std::string why;
921+
const bool same = oerr.empty() && perr.empty() && sameStream(oracle, proxy, why);
922+
if (!same && oerr.empty() && perr.empty()) {
923+
diag(" oracle stream: %s", streamSummary(oracle).c_str());
924+
diag(" proxy stream: %s", streamSummary(proxy).c_str());
925+
}
926+
ok(same, "mixed pool: DISCARD ALL in a transaction, extended protocol, on a backend opened under the other protocol [%s]: identical to direct PostgreSQL%s%s",
927+
MODE_NAME[mode], same ? "" : " -- ", same ? "" : why.c_str());
928+
// Put the pool and the setting back for the parity cases.
929+
if (!setNativeMode(admin, native) || !flushBackendPool(admin, BACKEND_HG, saved_servers)) {
930+
cleanup();
931+
BAIL_OUT("cannot reset the pool after the mixed case");
932+
}
933+
}
934+
899935
// ---- parity cases: the proxy's bytes must be PostgreSQL's bytes ------
900936
for (const auto& kase : PARITY_CASES) {
901937
if (kase.native_only && !native) {

0 commit comments

Comments
 (0)