From 5374de7575c3a399b3e829f14a6568b15dbb96c3 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH v16 8/8] 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 | 133 +++++++++++++++++- 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, 530 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 f3bf5bcf3d3..fdcfb80e451 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2397,6 +2397,22 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname + + + report_parameters + + + Specifies the value that is requested at connection startup for the + protocol + parameter. The connection attempt is not canceled if the server does + not support this protocol parameter, or if it only supports part of + the requested value. + Use the function after + connection has completed to find out what actual value the server uses. + + + + @@ -2714,7 +2730,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 @@ -2742,6 +2758,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. @@ -7679,6 +7699,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 *PQreportParametersSupport(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 if it failed 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 d60b0e9f616..920404bd750 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1335,9 +1335,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 @@ -1365,7 +1364,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. @@ -1736,6 +1739,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 c2eafea5319..14c97360639 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; static 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 c10c0844ab6..835a6ba6f17 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" @@ -226,7 +228,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 */ @@ -2092,7 +2095,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); @@ -2524,7 +2527,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; @@ -2574,7 +2577,7 @@ BeginReportingGUCOptions(void) { struct config_generic *conf = hentry->gucvar; - if (conf->flags & GUC_REPORT) + if (conf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) ReportGUCOption(conf); } } @@ -2617,8 +2620,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); } @@ -4265,7 +4276,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; @@ -6980,3 +6991,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 840b0fe57ff..1e29a386478 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" @@ -224,6 +225,8 @@ typedef enum 0x002000 /* can't set in PG_AUTOCONF_FILENAME */ #define GUC_RUNTIME_COMPUTED 0x004000 /* delay processing in 'postgres -C' */ #define GUC_ALLOW_IN_PARALLEL 0x008000 /* allow setting in parallel mode */ +#define GUC_REPORT_DYNAMIC 0x010000 /* 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 */ @@ -241,6 +244,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; @@ -443,6 +448,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 467f2718dec..84a496fe526 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -207,3 +207,7 @@ PQsetChunkedRowsMode 204 PQgetCurrentTimeUSec 205 PQservice 206 PQfullProtocolVersion 207 +PQsetReportParameters 208 +PQsendSetReportParameters 209 +PQreportParameters 210 +PQreportParametersSupport 211 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 846632f6541..827e5c2ef92 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -376,6 +376,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Max-Protocol-Version", "", 6, /* sizeof("latest") = 6 */ offsetof(struct pg_conn, max_protocol_version)}, + {"report_parameters", NULL, NULL, NULL, + "Report-Parameters", "", 40, + offsetof(struct pg_conn, c_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 29aaf42d334..279e0dba9fe 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 998e4f7f507..3d8553dcec0 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 5947e7c766f..b957d156318 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -608,6 +608,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 9438a13e8a6..cf053625ad4 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -431,6 +431,8 @@ struct pg_conn char *load_balance_hosts; /* load balance over hosts */ char *min_protocol_version; /* minimum used protocol version */ 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 @@ -491,6 +493,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 2eecdf21f5e..cc0990c1339 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -1561,6 +1561,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) { @@ -2232,6 +2350,7 @@ print_test_list(void) printf("pipelined_insert\n"); printf("prepared\n"); printf("protocol_version\n"); + printf("report_parameters\n"); printf("simple_pipeline\n"); printf("singlerow\n"); printf("transaction\n"); @@ -2353,6 +2472,8 @@ main(int argc, char **argv) test_prepared(conn); else if (strcmp(testname, "protocol_version") == 0) test_protocol_version(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..0e503afdf08 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_parameters)) > 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