Hi >From time to time I find myself in a situation where it would be very useful to be able to programatically determine whether a particular library is included in "shared_preload_libraries", which accepts a comma-separated list of values.
Unfortunately it's not as simple as splitting the list on the commas, as while that will *usually* work, the following is also valid: shared_preload_libraries = 'foo,bar,"baz ,"' and reliably splitting it up into its constituent parts would mean re-inventing a wheel (and worse possibly introducing some regular expressions into the process, cf. https://xkcd.com/1171/ ). Now, while it's highly unlikely someone will go to the trouble of creating a library name with commas and spaces in it, "highly unlikely" is not the same as "will definitely never ever happen". So it would be very handy to be able to use the same function PostgreSQL uses internally ("SplitDirectoriesString()") to produce the guaranteed same result. Attached patch provides a new function "pg_setting_value_split()" which does exactly this, i.e. called with a string containing such a list, it calls "SplitDirectoriesString()" and returns the result as a set of text, e.g.: postgres# SELECT setting FROM pg_setting_value_split('foo,bar,"baz ,"'); setting --------- foo bar baz , (3 rows) though a more likely use would be: SELECT setting FROM pg_setting_value_split(current_setting('shared_preload_libraries')); Other GUCs this applies to: - local_preload_libraries - session_preload_libraries - unix_socket_directories I will add this to the next CF. Regards Ian Barwick -- EnterpriseDB: https://www.enterprisedb.com
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index c99499e52b..263863ca72 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -23917,6 +23917,55 @@ SELECT collation for ('foo' COLLATE "de_DE"); </tgroup> </table> + <para> + <xref linkend="functions-admin-config-parse-table"/> shows the function + available to parse run-time configuration parameters. + </para> + + <table id="functions-admin-config-parse-table"> + <title>Configuration Parameter Parsing Function</title> + <tgroup cols="3"> + <thead> + <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row> + </thead> + <tbody> + <row> + <entry> + <indexterm> + <primary>pg_setting_value_split</primary> + </indexterm> + <literal><function>pg_setting_value_split(<parameter>setting</parameter>)</function></literal> + </entry> + <entry><type>setof text</type></entry> + <entry>split a parameter value into a set of <type>text</type> columns</entry> + </row> + </tbody> + </tgroup> + </table> + <para> + The function <function>pg_setting_value_split</function> takes a comma-separated list of values + (as would be provided as library names for the configuration parameters + <parameter>local_preload_libraries</parameter>, <parameter>session_preload_libraries</parameter> and + <parameter>shared_preload_libraries</parameter>, or as directory names for the configuration + parameter <parameter>unix_socket_directories</parameter>) and returns them as a set of + <type>text</type> columns, in the order they occur in the list. An example: +<programlisting> +SELECT setting FROM pg_setting_value_split('foo,bar,"baz ,"'); + + setting +--------- + foo + bar + baz , +(3 rows) + +</programlisting> + This function parses the comma-separated list as it would be when PostgreSQL parses + the configuration file, and is a reliable way of determining the presence of a + particular item in the list. + </para> + + </sect2> <sect2 id="functions-admin-signal"> diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index a62d64eaa4..d80dea4bbe 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -9784,6 +9784,93 @@ show_all_file_settings(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * setting_value_split + * + * Given a GUC such as "shared_preload_libraries" which may contain a + * comma-separated list of values, split these into individual items + * using SplitDirectoriesString and return as a list. + */ +Datum +setting_value_split(PG_FUNCTION_ARGS) +{ +#define NUM_PG_SETTINGS_VALUE_SPLIT_ATTS 1 + text *setting; + char *rawstring; + List *elemlist; + ListCell *l; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + /* Check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + if (PG_ARGISNULL(0)) + PG_RETURN_NULL(); + + setting = PG_GETARG_TEXT_PP(0); + rawstring = pstrdup(TextDatumGetCString(setting)); + + /* Parse string into list of filename paths */ + if (!SplitDirectoriesString(rawstring, ',', &elemlist)) + { + /* syntax error in list */ + list_free_deep(elemlist); + pfree(rawstring); + + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid list syntax"))); + } + + /* Switch into long-lived context to construct returned data structures */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* Build a tuple descriptor for our result type */ + tupdesc = CreateTemplateTupleDesc(NUM_PG_SETTINGS_VALUE_SPLIT_ATTS); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "setting", + TEXTOID, -1, 0); + + /* Build a tuplestore to return our results in */ + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + /* The rest can be done in short-lived context */ + MemoryContextSwitchTo(oldcontext); + + foreach(l, elemlist) + { + Datum values[NUM_PG_SETTINGS_VALUE_SPLIT_ATTS]; + bool nulls[NUM_PG_SETTINGS_VALUE_SPLIT_ATTS]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + values[0] = PointerGetDatum(cstring_to_text((char *) lfirst(l))); + + /* Place row into tuplestore */ + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + tuplestore_donestoring(tupstore); + pfree(rawstring); + + return (Datum) 0; +} + static char * _ShowOption(struct config_generic *record, bool use_units) { diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index bbcac69d48..6f625692a0 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5927,6 +5927,13 @@ proargmodes => '{o,o,o,o,o,o,o}', proargnames => '{sourcefile,sourceline,seqno,name,setting,applied,error}', prosrc => 'show_all_file_settings' }, +{ oid => '9744', descr => 'split comma-separated setting values into result set', + proname => 'pg_setting_value_split', prorows => '10', proretset => 't', + provolatile => 'v', prorettype => 'text', proargtypes => 'text', + proallargtypes => '{text}', + proargmodes => '{o}', + proargnames => '{setting}', + prosrc => 'setting_value_split' }, { oid => '3401', descr => 'show pg_hba.conf rules', proname => 'pg_hba_file_rules', prorows => '1000', proretset => 't', provolatile => 'v', prorettype => 'record', proargtypes => '', diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out index 811f80a097..8702867af0 100644 --- a/src/test/regress/expected/guc.out +++ b/src/test/regress/expected/guc.out @@ -776,3 +776,12 @@ set default_with_oids to f; -- Should not allow to set it to true. set default_with_oids to t; ERROR: tables declared WITH OIDS are not supported +-- check pg_setting_value_split() +SELECT setting FROM pg_setting_value_split('foo,bar,"baz ,"'); + setting +--------- + foo + bar + baz , +(3 rows) + diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql index 43dbba3775..bef0aa6918 100644 --- a/src/test/regress/sql/guc.sql +++ b/src/test/regress/sql/guc.sql @@ -296,3 +296,7 @@ reset check_function_bodies; set default_with_oids to f; -- Should not allow to set it to true. set default_with_oids to t; + +-- check pg_setting_value_split() + +SELECT setting FROM pg_setting_value_split('foo,bar,"baz ,"');