From fe694902b676cc51add8b8c3d48e495f258eedfc Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 10 Jan 2024 12:10:19 +0100 Subject: [PATCH v13 9/9] Add report_parameters protocol parameter Connection poolers use the ParameterStatus message to track changes in session level parameters. Before assinging a server connection to a client, the pooler will first restore the server parameters that the client expects. This is done to emulate session level SET commands in transaction pooling mode. Previously this could only be done for a hard-coded set of backend parameters, but with this change a connection pooler can choose for which additional backend parameters it wants to receive status changes. It can do this by setting the newly added report_parameters protocol parameter to a list of parameter names. --- doc/src/sgml/libpq.sgml | 117 ++++++++++++++++- doc/src/sgml/protocol.sgml | 38 +++++- src/backend/libpq/protocol-parameters.c | 7 + src/backend/utils/misc/guc.c | 109 +++++++++++++++- src/include/utils/guc.h | 6 + src/interfaces/libpq/exports.txt | 4 + src/interfaces/libpq/fe-connect.c | 4 + src/interfaces/libpq/fe-exec.c | 61 +++++++++ src/interfaces/libpq/fe-protocol3.c | 6 +- src/interfaces/libpq/libpq-fe.h | 5 + src/interfaces/libpq/libpq-int.h | 7 + .../modules/libpq_pipeline/libpq_pipeline.c | 121 ++++++++++++++++++ .../libpq_pipeline/t/001_libpq_pipeline.pl | 2 +- .../traces/report_parameters.trace | 41 ++++++ 14 files changed, 514 insertions(+), 14 deletions(-) create mode 100644 src/test/modules/libpq_pipeline/traces/report_parameters.trace diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 1d31e56daac..42a396765a7 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2632,7 +2632,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName); - Parameters reported as of the current release include: + The parameters that are reported by default as of the current release include: application_name client_encoding @@ -2658,6 +2658,10 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName); server_encoding and integer_datetimes cannot change after startup. + It's possible to choose additional parameters for which the values should + be reported by using the + protocol + parameter. @@ -7572,6 +7576,117 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit + + PQreportParametersPQreportParameter + + + + Returns the server value of the + protocol parameter. + +const char *PQreportParameters(const PGconn *conn); + + + This returns a string with a comma separated list of server parameter + names. If the protocol parameter is not supported by the server this + returns NULL instead. Checking this value for NULL is the recommended way + to check for server support of report_parameters. + + + + + + PQreportParametersSupportPQreportParametersSupport + + + + Returns the "supported values" of the + protocol parameter. + +const char *PQreportParameters(const PGconn *conn); + + + This returns a string indicating what features report_parameters supports, + on future protocol versions this might be more useful, but for now this + always returns 'L' if the report_parameters protocol + parameter is supported by the server. If the protocol parameter is not + supported by the server this returns NULL instead. + + + + + + PQsetReportParametersPQsetReportParameters + + + + Sends a request to set the value of the + protocol parameter. + +PGresult *PQsetReportParameters(PGconn *conn, const char *params); + + + conn is a connection to the server, + and params is a comma separated list of + parameter names for which the server should report their values. + use. If the request was not even sent, the function returns NULL; + The error message can be retrieved using + . + + + + Returns a PGresult pointer representing the + result of the command, or a null pointer if the routine failed before + issuing any command. + The function should be called + to check the return value for any errors (including the value of a null + pointer, in which case it will return + PGRES_FATAL_ERROR). Use + to get more information about + such errors. + + + + The status can be PGRES_COMMAND_OK if the command + succeeded, PGRES_FATAL_ERROR if it failed, or + PGRES_NONFATAL_ERROR but not in a way that required + aborting a running transaction. Use + to get more information about the + error when the status is PGRES_FATAL_ERROR. + + + + In case of success, use to + find the new value of the report_parameters protocol parameter. This new + value might be different than the value that was sent, due to e.g. + unknown parameters. + + + + + + PQsendSetReportParametersPQsendSetReportParameters + + + + Sends a request to set the value of the + protocol + parameter, without waiting for completion. + +int PQsendSetReportParameters(PGconn *conn, const char *params); + + + This is an asynchronous version of + : it + returns 1 if it was able to dispatch the request, and 0 if not. + After a successful call, call to + determine whether the server successfully set report_parameters. The + function's parameters are handled identically to + . + + + + PQtracePQtrace diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 8d33621a9fe..f63e4246be0 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1326,9 +1326,8 @@ SELCT 1/0; effective value. - - At present there is a hard-wired set of parameters for which - ParameterStatus will be generated. They are: + + The default parameters for which ParameterStatus will be generated are: application_name client_encoding @@ -1354,7 +1353,11 @@ SELCT 1/0; server_encoding and integer_datetimes are pseudo-parameters that cannot change after startup. - This set might change in the future, or even become configurable. + It's possible to choose additional parameters for which the values should + be reported using the + protocol parameter. + Since this set is configurable, a frontend can not assume that it only + gets these specific defaults. Accordingly, a frontend should simply ignore ParameterStatus for parameters that it does not understand or care about. @@ -1723,6 +1726,33 @@ SELCT 1/0; + + report_parameters + + report_parameters configuration parameter + + + + + This protocol parameter can be used to add parameters to the default + list of parameters that the server reports values for using the + ParameterStatus message. + This report_parameters can be used to + to report changes for additional parameters that are not reported by + default. The parameters that are in this list by default can be found in + . + + + + The value of this parameter is a comma separated list of parameter + names. Unknown parameters, and parameters that require the + pg_read_all_settings to be viewed are ignored. The + default value is the empty string. The server will use the literal value + 'L' as the supported version string in the + NegotiateProtocolParameter message. + + + diff --git a/src/backend/libpq/protocol-parameters.c b/src/backend/libpq/protocol-parameters.c index 4a41407d11b..3e3c66f3319 100644 --- a/src/backend/libpq/protocol-parameters.c +++ b/src/backend/libpq/protocol-parameters.c @@ -18,6 +18,7 @@ #include "libpq/pqformat.h" #include "tcop/tcopprot.h" #include "utils/memutils.h" +#include "utils/guc.h" static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error); @@ -25,6 +26,12 @@ static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool erro static MemoryContext ProtocolParameterMemoryContext; struct ProtocolParameter SupportedProtocolParameters[] = { + { + "report_parameters", + "", + report_parameters_handler, + .supported_string = "L", + } }; ProtocolParameter * diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 547cecde240..5db511cc475 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -35,6 +35,7 @@ #include "catalog/pg_authid.h" #include "catalog/pg_parameter_acl.h" #include "guc_internal.h" +#include "libpq/libpq-be.h" #include "libpq/pqformat.h" #include "libpq/protocol.h" #include "miscadmin.h" @@ -50,6 +51,7 @@ #include "utils/guc_tables.h" #include "utils/memutils.h" #include "utils/timestamp.h" +#include "utils/varlena.h" #define CONFIG_FILENAME "postgresql.conf" @@ -228,7 +230,8 @@ static slist_head guc_stack_list; /* list of variables that have non-NULL static slist_head guc_report_list; /* list of variables that have the * GUC_NEEDS_REPORT bit set in status */ -static bool reporting_enabled; /* true to enable GUC_REPORT */ +static bool reporting_enabled; /* true to enable GUC_REPORT and + * GUC_REPORT_DYNAMIC */ static int GUCNestLevel = 0; /* 1 when in main transaction */ @@ -2094,7 +2097,7 @@ ResetAllOptions(void) gconf->scontext = gconf->reset_scontext; gconf->srole = gconf->reset_srole; - if ((gconf->flags & GUC_REPORT) && !(gconf->status & GUC_NEEDS_REPORT)) + if ((gconf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) && !(gconf->status & GUC_NEEDS_REPORT)) { gconf->status |= GUC_NEEDS_REPORT; slist_push_head(&guc_report_list, &gconf->report_link); @@ -2526,7 +2529,7 @@ AtEOXact_GUC(bool isCommit, int nestLevel) pfree(stack); /* Report new value if we changed it */ - if (changed && (gconf->flags & GUC_REPORT) && + if (changed && (gconf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) && !(gconf->status & GUC_NEEDS_REPORT)) { gconf->status |= GUC_NEEDS_REPORT; @@ -2576,7 +2579,7 @@ BeginReportingGUCOptions(void) { struct config_generic *conf = hentry->gucvar; - if (conf->flags & GUC_REPORT) + if (conf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) ReportGUCOption(conf); } } @@ -2619,8 +2622,16 @@ ReportChangedGUCOptions(void) struct config_generic *conf = slist_container(struct config_generic, report_link, iter.cur); - Assert((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT)); - ReportGUCOption(conf); + Assert(conf->status & GUC_NEEDS_REPORT); + + /* + * The GUC_REPORT or GUC_REPORT_DYNAMIC is usually set if we reach + * here, but it's possible that GUC_REPORT_DYNAMIC was cleared just + * before due to a change in the value of report_parameter. + */ + if (conf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) + ReportGUCOption(conf); + conf->status &= ~GUC_NEEDS_REPORT; slist_delete_current(&iter); } @@ -4218,7 +4229,7 @@ set_config_with_handle(const char *name, config_handle *handle, } } - if (changeVal && (record->flags & GUC_REPORT) && + if (changeVal && (record->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) && !(record->status & GUC_NEEDS_REPORT)) { record->status |= GUC_NEEDS_REPORT; @@ -6939,3 +6950,87 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra, return true; } + +/* + * GUC check_hook for report_parameters + */ +const char * +report_parameters_handler(ProtocolParameter *param, const char *new_value) +{ + List *old_name_list, + *name_list; + char *old_protocol_params_str = pstrdup(param->value); + char *protocol_params_str = pstrdup(new_value); + StringInfo cleaned_value = makeStringInfo(); + + if (!SplitIdentifierString(old_protocol_params_str, ',', &old_name_list)) + { + elog(ERROR, "Previous list syntax is invalid."); + } + + if (!SplitIdentifierString(protocol_params_str, ',', &name_list)) + { + ereport(WARNING, + (errmsg("invalid list syntax for \"report_parameters\""))); + return NULL; + } + + /* + * Filter out the invalid GUCs from the new list and build a new + * normalized string. + */ + foreach_ptr(char, pname, name_list) + { + struct config_generic *config = find_option(pname, false, true, ERROR); + + if (!config || config->flags & GUC_SUPERUSER_ONLY) + continue; + if (cleaned_value->len > 0) + appendStringInfoString(cleaned_value, ","); + appendStringInfoString(cleaned_value, quote_identifier(config->name)); + } + + if (!SplitIdentifierString(old_protocol_params_str, ',', &old_name_list)) + { + elog(WARNING, "Normalized string is invalid"); + return NULL; + } + + /* + * First reset the flags for the previous list + */ + foreach_ptr(char, pname, old_name_list) + { + struct config_generic *config = find_option(pname, false, true, ERROR); + + if (!config || config->flags & GUC_SUPERUSER_ONLY) + continue; + + config->flags &= ~GUC_REPORT_DYNAMIC; + } + + /* + * Then apply the flags to the new list + */ + foreach_ptr(char, pname, name_list) + { + struct config_generic *config = find_option(pname, false, true, ERROR); + + if (!config || config->flags & GUC_SUPERUSER_ONLY) + continue; + + config->flags |= GUC_REPORT_DYNAMIC; + + /* force a report of this GUC */ + guc_free(config->last_reported); + config->last_reported = NULL; + if (!(config->status & GUC_NEEDS_REPORT) && IsNormalProcessingMode()) + { + config->status |= GUC_NEEDS_REPORT; + slist_push_head(&guc_report_list, &config->report_link); + } + + } + + return cleaned_value->data; +} diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index e4a594b5e80..ae4814bae48 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -13,6 +13,7 @@ #define GUC_H #include "nodes/parsenodes.h" +#include "libpq/libpq-be.h" #include "tcop/dest.h" #include "utils/array.h" @@ -223,6 +224,8 @@ typedef enum #define GUC_DISALLOW_IN_AUTO_FILE \ 0x002000 /* can't set in PG_AUTOCONF_FILENAME */ #define GUC_RUNTIME_COMPUTED 0x004000 /* delay processing in 'postgres -C' */ +#define GUC_REPORT_DYNAMIC 0x008000 /* used by report_parameters to + * auto-report */ #define GUC_UNIT_KB 0x01000000 /* value is in kilobytes */ #define GUC_UNIT_BLOCKS 0x02000000 /* value is in blocks */ @@ -240,6 +243,8 @@ typedef enum /* GUC vars that are actually defined in guc_tables.c, rather than elsewhere */ +extern PGDLLIMPORT char *report_parameters; + extern PGDLLIMPORT bool Debug_print_plan; extern PGDLLIMPORT bool Debug_print_parse; extern PGDLLIMPORT bool Debug_print_rewritten; @@ -414,6 +419,7 @@ extern void *guc_malloc(int elevel, size_t size); extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size); extern char *guc_strdup(int elevel, const char *src); extern void guc_free(void *ptr); +extern const char *report_parameters_handler(ProtocolParameter *param, const char *new_value); #ifdef EXEC_BACKEND extern void write_nondefault_variables(GucContext context); diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 8ee08115100..e35602ab263 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -204,3 +204,7 @@ PQcancelReset 201 PQcancelFinish 202 PQsocketPoll 203 PQsetChunkedRowsMode 204 +PQsetReportParameters 205 +PQsendSetReportParameters 206 +PQreportParameters 207 +PQreportParametersSupport 208 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index c4d8516079e..92931b14785 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -369,6 +369,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Max-Protocol-Version", "", 3, /* sizeof("3.2") = 3 */ offsetof(struct pg_conn, max_protocol_version)}, + {"report_parameters", NULL, NULL, NULL, + "Report-Parameters", "", 40, + offsetof(struct pg_conn, report_parameters)}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index da42dd5a626..0c062b398a4 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -3414,6 +3414,67 @@ PQsendFlushRequest(PGconn *conn) return 1; } +/* + * PQreportParameters + * Return the current server-side report_parameters string. + */ +const char * +PQreportParameters(PGconn *conn) +{ + return conn->report_parameters; +} + +/* + * PQreportParametersSupport + * Return the server's support for the report_parameters protocol parameter. + */ +const char * +PQreportParametersSupport(PGconn *conn) +{ + return conn->report_parameters_support; +} + +/* + * PQsetReportParameters + * Send a request for the server to report the given parameters. + * + * Params should be a comma separated list of parameter names. Overwrites any + * previous attempt list of parameters. + * If the request was not even sent, return NULL; conn->errorMessage is set + * to a relevant message. + * If the request was sent, a new PGresult is returned (which could indicate + * either success or failure). On success, the PGresult contains status + * PGRES_COMMAND_OK. The user is responsible for freeing the PGresult via + * PQclear() when done with it. + */ +PGresult * +PQsetReportParameters(PGconn *conn, const char *params) +{ + if (!PQreportParameters(conn)) + { + libpq_append_conn_error(conn, "server does not support report_parameters"); + return NULL; + } + return PQsetProtocolParameter(conn, "report_parameters", params); +} + +/* + * PQsetReportParameters + * Submit a request for the server to report the given parameters, but don't wait for it to finish. + * + * Returns 1 on success and 0 on failure. + */ +int +PQsendSetReportParameters(PGconn *conn, const char *params) +{ + if (!PQreportParameters(conn)) + { + libpq_append_conn_error(conn, "server does not support report_parameters"); + return 0; + } + return PQsendSetProtocolParameter(conn, "report_parameters", params); +} + /* * PQsetProtocolParameter * Send a request for the server to change a protocol parameter. diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index be199621d4d..2681e16ca66 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -58,7 +58,11 @@ static int build_startup_packet(const PGconn *conn, char *packet, const PQEnvironmentOption *options); const struct pg_protocol_parameter KnownProtocolParameters[] = { - {NULL, NULL, 0, 0} + {"report_parameters", "", + offsetof(PGconn, c_report_parameters), + offsetof(PGconn, report_parameters), + offsetof(PGconn, report_parameters_support)}, + {NULL, NULL, 0, 0, 0} }; /* diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 73f6e65ae55..7c20e74dd12 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -583,6 +583,11 @@ extern PGresult *PQclosePortal(PGconn *conn, const char *portal); extern int PQsendClosePrepared(PGconn *conn, const char *stmt); extern int PQsendClosePortal(PGconn *conn, const char *portal); +extern const char *PQreportParameters(PGconn *conn); +const char *PQreportParametersSupport(PGconn *conn); +extern PGresult *PQsetReportParameters(PGconn *conn, const char *params); +extern int PQsendSetReportParameters(PGconn *conn, const char *params); + /* Delete a PGresult */ extern void PQclear(PGresult *res); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index ebcf8279b27..15754bfcee0 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -417,6 +417,8 @@ struct pg_conn char *require_auth; /* name of the expected auth method */ char *load_balance_hosts; /* load balance over hosts */ char *max_protocol_version; /* maximum used protocol version */ + char *c_report_parameters; /* report_parameters value from the + * connection string */ bool cancelRequest; /* true if this connection is used to send a * cancel request, instead of being a normal @@ -477,6 +479,11 @@ struct pg_conn SockAddr laddr; /* Local address */ SockAddr raddr; /* Remote address */ ProtocolVersion pversion; /* FE/BE protocol version in use */ + char *report_parameters; /* report_parameters value of the server, + * NULL if not supported */ + char *report_parameters_support; /* report_parameters server + * support string, NULL if not + * supported */ int sversion; /* server version, e.g. 70401 for 7.4.1 */ bool auth_req_received; /* true if any type of auth req received */ bool password_needed; /* true if server demanded a password */ diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c index f93778b92f5..bb754b413ec 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -1486,6 +1486,124 @@ test_pipeline_idle(PGconn *conn) fprintf(stderr, "ok - 2\n"); } +static void +test_report_parameters(PGconn *conn) +{ + PGresult *res = NULL; + const char *val = NULL; + + res = PQexec(conn, "SET application_name = 'test1'"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set parameter: %s", PQerrorMessage(conn)); + + val = PQparameterStatus(conn, "application_name"); + if (val == NULL) + pg_fatal("expected application_name to be tracked, but wasn't"); + if (strcmp(val, "test1") != 0) + pg_fatal("expected application_name to tracked as 'test1', but was '%s'", val); + + res = PQexec(conn, "SET application_name = 'test2'"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set parameter: %s", PQerrorMessage(conn)); + + val = PQparameterStatus(conn, "application_name"); + if (strcmp(val, "test2") != 0) + pg_fatal("expected application_name to tracked as 'test2', but was '%s'", val); + + res = PQsetReportParameters(conn, "lock_timeout"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn)); + + val = PQreportParameters(conn); + if (strcmp(val, "lock_timeout") != 0) + pg_fatal("expected report_parameters to be 'lock_timeout', but was '%s'", val); + + /* Should have automatically received initial value */ + val = PQparameterStatus(conn, "lock_timeout"); + if (strcmp(val, "0") != 0) + pg_fatal("expected application_name to tracked as '123000', but was '%s'", val); + + /* + * Add some more parameters to track, including a GUC_SUPERUSER_ONLY and a + * non-existent one. + */ + if (PQsendSetReportParameters(conn, "\"lock_timeout\" , shared_preload_libraries,work_mem ,does_not_exist") != 1) + pg_fatal("PQsendSetReportParameters failed: %s", PQerrorMessage(conn)); + res = PQgetResult(conn); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn)); + res = PQgetResult(conn); + if (res != NULL) + pg_fatal("expected null result"); + + /* + * Should have filtered out the does_not_exist & shared_preload_libraries, + * and should have removed spaces + */ + val = PQreportParameters(conn); + if (strcmp(val, "lock_timeout,work_mem") != 0) + pg_fatal("expected report_parameters to be 'lock_timeout,work_mem', but was '%s'", val); + + val = PQparameterStatus(conn, "lock_timeout"); + if (strcmp(val, "0") != 0) + pg_fatal("expected application_name to tracked as '123000', but was '%s'", val); + + val = PQparameterStatus(conn, "work_mem"); + if (strcmp(val, "4096") != 0) + pg_fatal("expected work_mem to be tracked as '4096', but was '%s'", val); + + /* + * changes to application_name should always be tracked even if it is not + * in report_parameters + */ + res = PQexec(conn, "SET application_name = 'test3333'"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set parameter: %s", PQerrorMessage(conn)); + + val = PQparameterStatus(conn, "application_name"); + if (strcmp(val, "test3333") != 0) + pg_fatal("expected application_name to tracked as 'test2', but was '%s'", val); + + /* changes to and work_mem lock_timeout should be tracked */ + res = PQexec(conn, "SET lock_timeout = '123s'"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set parameter: %s", PQerrorMessage(conn)); + + val = PQparameterStatus(conn, "lock_timeout"); + if (strcmp(val, "123000") != 0) + pg_fatal("expected application_name to tracked as '123000', but was '%s'", val); + + /* only track work_mem again */ + res = PQsetReportParameters(conn, "work_mem"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn)); + + /* changes to lock_timeout should not be tracked anymore now */ + res = PQexec(conn, "SET lock_timeout = '345s'"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set parameter: %s", PQerrorMessage(conn)); + + val = PQparameterStatus(conn, "lock_timeout"); + if (strcmp(val, "123000") != 0) + pg_fatal("expected application_name to tracked as '123000', but was '%s'", val); + + /* soft-failure case */ + res = PQsetReportParameters(conn, ","); + if (PQresultStatus(res) == PGRES_FATAL_ERROR) + pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn)); + if (PQresultStatus(res) != PGRES_NONFATAL_ERROR) + pg_fatal("expected to receive a non-fatal error when changing report_parameters"); + + res = PQexec(conn, "SET work_mem = '123456'"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to set parameter: %s", PQerrorMessage(conn)); + + /* work_mem should still be tracked because of failure */ + val = PQparameterStatus(conn, "work_mem"); + if (strcmp(val, "123456") != 0) + pg_fatal("expected application_name to tracked as '123000', but was '%s'", val); +} + static void test_simple_pipeline(PGconn *conn) { @@ -2156,6 +2274,7 @@ print_test_list(void) printf("pipeline_idle\n"); printf("pipelined_insert\n"); printf("prepared\n"); + printf("report_parameters\n"); printf("simple_pipeline\n"); printf("singlerow\n"); printf("transaction\n"); @@ -2275,6 +2394,8 @@ main(int argc, char **argv) test_pipelined_insert(conn, numrows); else if (strcmp(testname, "prepared") == 0) test_prepared(conn); + else if (strcmp(testname, "report_parameters") == 0) + test_report_parameters(conn); else if (strcmp(testname, "simple_pipeline") == 0) test_simple_pipeline(conn); else if (strcmp(testname, "singlerow") == 0) diff --git a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl index f9e6d07fc0b..698549c5210 100644 --- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl +++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl @@ -39,7 +39,7 @@ for my $testname (@tests) my $cmptrace = grep(/^$testname$/, qw(simple_pipeline nosync multi_pipelines prepared singlerow pipeline_abort pipeline_idle transaction - disallowed_in_pipeline)) > 0; + disallowed_in_pipeline report_paramters)) > 0; # For a bunch of tests, generate a libpq trace file too. my $traceout = diff --git a/src/test/modules/libpq_pipeline/traces/report_parameters.trace b/src/test/modules/libpq_pipeline/traces/report_parameters.trace new file mode 100644 index 00000000000..9678778c965 --- /dev/null +++ b/src/test/modules/libpq_pipeline/traces/report_parameters.trace @@ -0,0 +1,41 @@ +F 35 Query "SET application_name = 'test1'" +B 8 CommandComplete "SET" +B 27 ParameterStatus "application_name" "test1" +B 5 ReadyForQuery I +F 35 Query "SET application_name = 'test2'" +B 8 CommandComplete "SET" +B 27 ParameterStatus "application_name" "test2" +B 5 ReadyForQuery I +F 35 SetProtocolParameter "report_parameters" "lock_timeout" +B 36 SetProtocolParameterComplete "report_parameters" "lock_timeout" S +B 19 ParameterStatus "lock_timeout" "0" +B 5 ReadyForQuery I +F 95 SetProtocolParameter "report_parameters" ""lock_timeout" , shared_preload_libraries,work_mem ,does_not_exist" +B 45 SetProtocolParameterComplete "report_parameters" "lock_timeout,work_mem" S +B 18 ParameterStatus "work_mem" "4096" +B 19 ParameterStatus "lock_timeout" "0" +B 5 ReadyForQuery I +F 38 Query "SET application_name = 'test3333'" +B 8 CommandComplete "SET" +B 30 ParameterStatus "application_name" "test3333" +B 5 ReadyForQuery I +F 30 Query "SET lock_timeout = '123s'" +B 8 CommandComplete "SET" +B 24 ParameterStatus "lock_timeout" "123000" +B 5 ReadyForQuery I +F 31 SetProtocolParameter "report_parameters" "work_mem" +B 32 SetProtocolParameterComplete "report_parameters" "work_mem" S +B 18 ParameterStatus "work_mem" "4096" +B 5 ReadyForQuery I +F 30 Query "SET lock_timeout = '345s'" +B 8 CommandComplete "SET" +B 5 ReadyForQuery I +F 24 SetProtocolParameter "report_parameters" "," +B NN NoticeResponse S "WARNING" V "WARNING" C "01000" M "invalid list syntax for "report_parameters"" F "SSSS" L "SSSS" R "SSSS" \x00 +B 32 SetProtocolParameterComplete "report_parameters" "work_mem" E +B 5 ReadyForQuery I +F 28 Query "SET work_mem = '123456'" +B 8 CommandComplete "SET" +B 20 ParameterStatus "work_mem" "123456" +B 5 ReadyForQuery I +F 4 Terminate -- 2.34.1