psql \df choose functions by their arguments
-----BEGIN PGP SIGNED MESSAGE-----
Hash: RIPEMD160
Improve psql \df to choose functions by their arguments
== OVERVIEW
Having to scroll through same-named functions with different argument types
when you know exactly which one you want is annoying at best, error causing
at worst. This patch enables a quick narrowing of functions with the
same name but different arguments. For example, to see the full details
of a function names "myfunc" with a TEXT argument, but not showing
the version of "myfunc" with a BIGINT argument, one can now do:
psql=# \df myfunc text
For this, we are fairly liberal in what we accept, and try to be as
intuitive as possible.
Features:
* Type names are case insensitive. Whitespace is optional, but quoting is
respected:
greg=# \df myfunc text "character varying" INTEGER
* Abbreviations of common types is permitted (because who really likes
to type out "character varying"?), so the above could also be written as:
greg=# \df myfunc text varchar int
* The matching is greedy, so you can see everything matching a subset:
greg=# \df myfunc timestamptz
List of functions
Schema | Name | Result data type | Argument data types
| Type
-
--------+--------+------------------+-------------------------------------------+------
public | myfunc | void | timestamp with time zone
| func
public | myfunc | void | timestamp with time zone, bigint
| func
public | myfunc | void | timestamp with time zone, bigint,
boolean | func
public | myfunc | void | timestamp with time zone, integer
| func
public | myfunc | void | timestamp with time zone, text, cidr
| func
(5 rows)
* The appearance of a closing paren indicates we do not want the greediness:
greg=# \df myfunc (timestamptz, bigint)
List of functions
Schema | Name | Result data type | Argument data types |
Type
-
--------+--------+------------------+----------------------------------+------
public | myfunc | void | timestamp with time zone, bigint |
func
(1 row)
== TAB COMPLETION:
I'm not entirely happy with this, but I figure piggybacking
onto COMPLETE_WITH_FUNCTION_ARG is better than nothing at all.
Ideally we'd walk prev*_wd to refine the returned list, but
that's an awful lot of complexity for very little gain, and I think
the current behavior of showing the complete list of args each time
should suffice.
== DOCUMENTATION:
The new feature is briefly mentioned: wordsmithing help in the
sgml section is appreciated. I'm not sure how many of the above features
need to be documented in detail.
Regarding psql/help.c, I don't think this really warrants a change there.
As it is, we've gone through great lengths to keep this overloaded
backslash
command left justified with the rest!
== TESTS:
I put this into psql.c, seems the best place. Mostly testing out
basic functionality, quoting, and the various abbreviations. Not much
else to test, near as I can tell, as this is a pure convienence addition
and shouldn't affect anything else. Any extra words after a function name
for \df was previously treated as an error.
== IMPLEMENTATION:
Rather than messing with psqlscanslash, we simply slurp in the entire rest
of the line via psql_scan_slash_option (all of which was previously
ignored).
This is passed to describeFunction, which then uses strtokx to break it
into tokens. We look for a match by comparing the current proargtypes
entry,
casted to text, against the lowercase version of the token found by
strtokx.
Along the way, we convert things like "timestamptz" to the official version
(i.e. "timestamp with time zone"). If any of the tokens start with a
closing
paren, we immediately stop parsing and set pronargs to the current number
of valid tokens, thereby forcing a match to one (or zero) functions.
6ab7a45d541f2c31c5631b811f14081bf7b22271
v1-psql-df-pick-function-by-type.patch
- --
Greg Sabino Mullane
PGP Key: 0x14964AC8 202010151316
http://biglumber.com/x/web?pk=2529DF6AB8F79407E94445B4BC9B906714964AC8
-----BEGIN PGP SIGNATURE-----
iF0EAREDAB0WIQQlKd9quPeUB+lERbS8m5BnFJZKyAUCX4iENQAKCRC8m5BnFJZK
yIUKAKDiv1E9KgXuSO7lE9p+ttFdk02O2ACg44lu9VdKt3IggIrPiXBPKR8C85M=
=QPSd
-----END PGP SIGNATURE-----
Attachments:
v1-psql-df-pick-function-by-type.patchapplication/x-patch; name=v1-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ee3fc09577..c63255cebc 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1548,7 +1548,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ] [ types ]</literal></term>
<listitem>
<para>
@@ -1561,6 +1561,7 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional words are considered type arguments to help narrow the list of returned functions.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1572,7 +1573,7 @@ testdb=>
<tip>
<para>
- To look up functions taking arguments or returning values of a specific
+ To look up functions returning values of a specific
data type, use your pager's search capability to scroll through the
<literal>\df</literal> output.
</para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index d4aa0976b5..72354a5d37 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -783,6 +783,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'f': /* function subsystem */
switch (cmd[2])
{
+ char *funcargs;
+
case '\0':
case '+':
case 'S':
@@ -791,7 +793,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
+ free(funcargs);
break;
default:
status = PSQL_CMD_UNKNOWN;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 6bb0316bd9..52a266acad 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -26,6 +26,7 @@
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "settings.h"
+#include "stringutils.h"
#include "variables.h"
static bool describeOneTableDetails(const char *schemaname,
@@ -312,7 +313,7 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -626,6 +627,49 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
+ /*
+ * Check for any additional arguments to narrow down which functions are
+ * desired
+ */
+ if (funcargs)
+ {
+
+ int x = 0;
+ int argoffset = 0;
+ char *functoken;
+
+ while ((functoken = strtokx(x++ ? NULL : funcargs, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
+ {
+ if (isalpha(functoken[0]))
+ {
+ appendPQExpBuffer(&buf, " AND p.proargtypes[%d]::regtype::text = LOWER(%s)::text\n",
+ argoffset++,
+
+ /*
+ * This is not a comprehensive list - just a little help
+ */
+ pg_strcasecmp(functoken, "bool") == 0 ? "'boolean'"
+ : pg_strcasecmp(functoken, "char") == 0 ? "'character'"
+ : pg_strcasecmp(functoken, "double") == 0 ? "'double precision'"
+ : pg_strcasecmp(functoken, "float") == 0 ? "'double precision'"
+ : pg_strcasecmp(functoken, "int") == 0 ? "'integer'"
+ : pg_strcasecmp(functoken, "time") == 0 ? "'time without time zone'"
+ : pg_strcasecmp(functoken, "timetz") == 0 ? "'time with time zone'"
+ : pg_strcasecmp(functoken, "timestamp") == 0 ? "'timestamp without time zone'"
+ : pg_strcasecmp(functoken, "timestamptz") == 0 ? "'timestamp with time zone'"
+ : pg_strcasecmp(functoken, "varbit") == 0 ? "'bit varying'"
+ : pg_strcasecmp(functoken, "varchar") == 0 ? "'character varying'"
+ : PQescapeLiteral(pset.db, functoken, strlen(functoken)));
+
+ }
+ else if (functoken[0] == ')' && argoffset)
+ { /* Force limit the number of args */
+ appendPQExpBuffer(&buf, " AND p.pronargs = %d\n", argoffset);
+ break;
+ }
+ }
+ }
+
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index f0e3ec957c..a3400afb8c 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,7 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 561fe1dff9..8c51cf6e37 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3786,6 +3786,8 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_QUERY(Query_for_list_of_fdws);
else if (TailMatchesCS("\\df*"))
COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_functions, NULL);
+ else if (HeadMatches("\\df", MatchAny))
+ COMPLETE_WITH_FUNCTION_ARG(previous_words[previous_words_count - 2]);
else if (TailMatchesCS("\\dFd*"))
COMPLETE_WITH_QUERY(Query_for_list_of_ts_dictionaries);
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index daac0ff49d..fd69956d07 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5072,3 +5072,110 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- list specific functions of the same name but different args
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+-- With no arguments, all functions are shown
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | bit varying | func
+ public | mtest | integer | boolean, character, character varying | func
+ public | mtest | integer | double precision, double precision, integer | func
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+ public | mtest | integer | time without time zone, time with time zone | func
+(7 rows)
+
+-- An invalid argument type matches nothing
+\df mtest mint
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+(2 rows)
+
+-- Two argument types match up
+\df mtest integer text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+(1 row)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------+------
+ public | mtest | integer | boolean, character, character varying | func
+(1 row)
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | double precision, double precision, integer | func
+(1 row)
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (time timetz)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | time without time zone, time with time zone | func
+(1 row)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestampt with time zone
+\df mtest timestamp timestamptz
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(1 row)
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | bit varying | func
+(1 row)
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 47b28d2a07..73f4d7cf90 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1225,3 +1225,53 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+
+-- list specific functions of the same name but different args
+
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+
+-- With no arguments, all functions are shown
+\df mtest
+
+-- An invalid argument type matches nothing
+\df mtest mint
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+
+-- Two argument types match up
+\df mtest integer text
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (time timetz)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestampt with time zone
+\df mtest timestamp timestamptz
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+
+\df mtest
On Thu, Oct 15, 2020 at 01:21:06PM -0400, Greg Sabino Mullane wrote:
Improve psql \df to choose functions by their arguments
I think this is a good idea.
This isn't working for arrays:
postgres=# \df aa
public | aa | integer | integer, integer | func
public | aa | integer | integer, integer, integer | func
public | aa | integer | integer[], integer, integer | func
postgres=# \df aa aa int[]
I think it should use the same syntax as \sf and \ef, which require parenthesis
and commas, not spaces.
int x = 0
while ((functoken = strtokx(x++ ? NULL : funcargs, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
I think x is just used as "initial", so I think you should make it boolean and
then set is_initial = false, or similar.
+ pg_strcasecmp(functoken, "bool") == 0 ? "'boolean'"
I think writing this all within a call to appendPQExpBuffer() is excessive.
You can make an array or structure to search through and then append the result
to the buffer.
--
Justin
Thank you for looking this over.
This isn't working for arrays:
...
postgres=# \df aa aa int[]
Arrays should work as expected, I think you have one too many "aa" in there?
I think it should use the same syntax as \sf and \ef, which require
parenthesis
and commas, not spaces.
Hmm, that will not allow partial matches if we require a closing parens.
Right now both commas and parens are accepted, but optional.
I think x is just used as "initial", so I think you should make it boolean
and
then set is_initial = false, or similar.
Good suggestion, it is done.
+
pg_strcasecmp(functoken, "bool") == 0 ? "'boolean'"I think writing this all within a call to appendPQExpBuffer() is excessive.
You can make an array or structure to search through and then append the
result
to the buffer.
Hmm, like a custom struct we loop through? I will look into implementing
that and submit a new patch.
Cheers,
Greg
Thanks for the feedback, attached is version two of the patch. Major
changes:
* Use booleans not generic "int x"
* Build a quick list of abbreviations at the top of the function
* Add array mapping for all types
* Removed the tab-complete bit, it was too fragile and unhelpful
Cheers,
Greg
Attachments:
v2-psql-df-pick-function-by-type.patchtext/x-patch; charset=US-ASCII; name=v2-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 221a967bfe..cf3e8c7134 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1574,7 +1574,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ] [ types ]</literal></term>
<listitem>
<para>
@@ -1587,6 +1587,7 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional words are considered type arguments to help narrow the list of returned functions.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1598,7 +1599,7 @@ testdb=>
<tip>
<para>
- To look up functions taking arguments or returning values of a specific
+ To look up functions returning values of a specific
data type, use your pager's search capability to scroll through the
<literal>\df</literal> output.
</para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index c7a83d5dfc..426603b0cb 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -783,6 +783,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'f': /* function subsystem */
switch (cmd[2])
{
+ char *funcargs;
+
case '\0':
case '+':
case 'S':
@@ -791,7 +793,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
+ free(funcargs);
break;
default:
status = PSQL_CMD_UNKNOWN;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d640021c..a8d3f3ba53 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -26,6 +26,7 @@
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "settings.h"
+#include "stringutils.h"
#include "variables.h"
static bool describeOneTableDetails(const char *schemaname,
@@ -312,7 +313,7 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -626,6 +627,67 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
+ /*
+ * Check for any additional arguments to narrow down which functions are
+ * desired
+ */
+ if (funcargs)
+ {
+
+ bool is_initial_run = true;
+ bool found_abbreviation;
+ int argoffset = 0;
+ char *functoken;
+
+ static const char *type_abbreviations[] = {
+ "bool", "boolean", "bool[]", "boolean[]",
+ "char", "character", "char[]", "character[]",
+ "double", "double precision", "double[]", "double precision[]",
+ "float", "double precision", "float[]", "double precision[]",
+ "int", "integer", "int[]", "integer[]",
+ "time", "time without time zone", "time[]", "time without time zone[]",
+ "timetz", "time with time zone", "timetz[]", "time with time zone[]",
+ "timestamp", "timestamp without timestamp zone", "timestamp[]", "timestamp without timestamp zone[]",
+ "timestamptz", "timestamp with timestamp zone", "timestamptz[]", "timestamp with timestamp zone[]",
+ "varbit", "bit varying", "varbit[]", "bit varying[]",
+ "varchar", "character varying", "varchar[]", "character varying[]",
+ NULL
+ };
+
+ while ((functoken = strtokx(is_initial_run ? funcargs : NULL, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
+ {
+ is_initial_run = false;
+ found_abbreviation = false;
+
+ if (isalpha(functoken[0]))
+ {
+ appendPQExpBuffer(&buf, " AND p.proargtypes[%d]::regtype::text = ", argoffset++);
+ for (int i = 0; NULL != *(type_abbreviations + i); i += 2)
+ {
+ const char *shortname = *(type_abbreviations + i);
+ const char *longname = *(type_abbreviations + i + 1);
+
+ if (pg_strcasecmp(functoken, shortname) == 0)
+ {
+ appendPQExpBuffer(&buf, "LOWER('%s')::text\n", longname);
+ found_abbreviation = true;
+ break;
+ }
+ }
+ if (!found_abbreviation)
+ {
+ appendPQExpBuffer(&buf, "LOWER(%s)::text\n", PQescapeLiteral(pset.db, functoken, strlen(functoken)));
+ }
+
+ }
+ else if (functoken[0] == ')' && argoffset)
+ { /* Force limit the number of args */
+ appendPQExpBuffer(&buf, " AND p.pronargs = %d\n", argoffset);
+ break;
+ }
+ }
+ }
+
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index f0e3ec957c..a3400afb8c 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,7 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index daac0ff49d..fd69956d07 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5072,3 +5072,110 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- list specific functions of the same name but different args
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+-- With no arguments, all functions are shown
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | bit varying | func
+ public | mtest | integer | boolean, character, character varying | func
+ public | mtest | integer | double precision, double precision, integer | func
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+ public | mtest | integer | time without time zone, time with time zone | func
+(7 rows)
+
+-- An invalid argument type matches nothing
+\df mtest mint
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+(2 rows)
+
+-- Two argument types match up
+\df mtest integer text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+(1 row)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------+------
+ public | mtest | integer | boolean, character, character varying | func
+(1 row)
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | double precision, double precision, integer | func
+(1 row)
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (time timetz)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | time without time zone, time with time zone | func
+(1 row)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestampt with time zone
+\df mtest timestamp timestamptz
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(1 row)
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | bit varying | func
+(1 row)
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 47b28d2a07..73f4d7cf90 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1225,3 +1225,53 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+
+-- list specific functions of the same name but different args
+
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+
+-- With no arguments, all functions are shown
+\df mtest
+
+-- An invalid argument type matches nothing
+\df mtest mint
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+
+-- Two argument types match up
+\df mtest integer text
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (time timetz)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestampt with time zone
+\df mtest timestamp timestamptz
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+
+\df mtest
* Removed the tab-complete bit, it was too fragile and unhelpful
I can’t speak for the specific patch, but tab completion of proc args for \df, \ef and friends has long been a desired feature of mine, particularly when you are dealing with functions with huge numbers of arguments and the same name which I have (sadly) come across many times in the wild.
Removing this because it was brittle is fine, but would be good to see if we could figure out a way to have this kind of feature in psql IMHO.
Best,
David
Hi
(sorry forget to cc the hacklist)
Improve psql \df to choose functions by their arguments
I think this is useful.
I found some comments in the patch.
1.
* Abbreviations of common types is permitted (because who really likes
to type out "character varying"?), so the above could also be written as:
some Abbreviations of common types are not added to the type_abbreviations[] Such as:
Int8 => bigint
Int2 => smallint
Int4 ,int => integer
Float4 => real
Float8,float,double => double precision
(as same as array type)
Single array seems difficult to handle it, may be we can use double array or use a struct.
2.
And I think It's better to update '/?' info about '\df[+]' in function slashUsage(unsigned short int pager).
Best regards,
houzj
Thanks for looking this over!
some Abbreviations of common types are not added to the
type_abbreviations[] Such as:Int8 => bigint
I wasn't aiming to provide a canonical list, as I personally have never
seen anyone use int8 instead of bigint when (for example) creating a
function, but I'm not strongly opposed to expanding the list.
Single array seems difficult to handle it, may be we can use double array
or use a struct.
I think the single works out okay, as this is a simple write-once variable
that is not likely to get updated often.
And I think It's better to update '/?' info about '\df[+]' in function
slashUsage(unsigned short int pager).
Suggestions welcome, but it's already pretty tight in there, so I couldn't
think of anything:
fprintf(output, _(" \\dew[+] [PATTERN] list foreign-data
wrappers\n"));
fprintf(output, _(" \\df[anptw][S+] [PATRN] list [only
agg/normal/procedures/trigger/window] functions\n"));
fprintf(output, _(" \\dF[+] [PATTERN] list text search
configurations\n"));
The \df option is already our longest one, even with the silly attempt to
shorten PATTERN :)
Cheers,
Greg
On Sun, Nov 1, 2020 at 12:05 PM David Christensen <david@pgguru.net> wrote:
I can’t speak for the specific patch, but tab completion of proc args for
\df, \ef and friends has long been a desired feature of mine, particularly
when you are dealing with functions with huge numbers of arguments and the
same name which I have (sadly) come across many times in the wild.
If someone can get this working against this current patch, that would be
great, but I suspect it will require some macro-jiggering in tab-complete.c
and possibly more, so yeah, could be something to add to the todo list.
Cheers,
Greg
Attached is the latest patch against HEAD - basically fixes a few typos.
Cheers,
Greg
Attachments:
v3-psql-df-pick-function-by-type.patchapplication/octet-stream; name=v3-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 221a967bfe..cf3e8c7134 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1574,7 +1574,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ] [ types ]</literal></term>
<listitem>
<para>
@@ -1587,6 +1587,7 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional words are considered type arguments to help narrow the list of returned functions.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1598,7 +1599,7 @@ testdb=>
<tip>
<para>
- To look up functions taking arguments or returning values of a specific
+ To look up functions returning values of a specific
data type, use your pager's search capability to scroll through the
<literal>\df</literal> output.
</para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 38b588882d..746df9708e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -781,6 +781,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'f': /* function subsystem */
switch (cmd[2])
{
+ char *funcargs;
+
case '\0':
case '+':
case 'S':
@@ -789,7 +791,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
+ free(funcargs);
break;
default:
status = PSQL_CMD_UNKNOWN;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 14150d05a9..edee67b08b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -26,6 +26,7 @@
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "settings.h"
+#include "stringutils.h"
#include "variables.h"
static bool describeOneTableDetails(const char *schemaname,
@@ -312,7 +313,7 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -626,6 +627,67 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
+ /*
+ * Check for any additional arguments to narrow down which functions are
+ * desired
+ */
+ if (funcargs)
+ {
+
+ bool is_initial_run = true;
+ bool found_abbreviation;
+ int argoffset = 0;
+ char *functoken;
+
+ static const char *type_abbreviations[] = {
+ "bool", "boolean", "bool[]", "boolean[]",
+ "char", "character", "char[]", "character[]",
+ "double", "double precision", "double[]", "double precision[]",
+ "float", "double precision", "float[]", "double precision[]",
+ "int", "integer", "int[]", "integer[]",
+ "time", "time without time zone", "time[]", "time without time zone[]",
+ "timetz", "time with time zone", "timetz[]", "time with time zone[]",
+ "timestamp", "timestamp without time zone", "timestamp[]", "timestamp without time zone[]",
+ "timestamptz", "timestamp with time zone", "timestamptz[]", "timestamp with time zone[]",
+ "varbit", "bit varying", "varbit[]", "bit varying[]",
+ "varchar", "character varying", "varchar[]", "character varying[]",
+ NULL
+ };
+
+ while ((functoken = strtokx(is_initial_run ? funcargs : NULL, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
+ {
+ is_initial_run = false;
+ found_abbreviation = false;
+
+ if (isalpha(functoken[0]))
+ {
+ appendPQExpBuffer(&buf, " AND p.proargtypes[%d]::regtype::text = ", argoffset++);
+ for (int i = 0; NULL != *(type_abbreviations + i); i += 2)
+ {
+ const char *shortname = *(type_abbreviations + i);
+ const char *longname = *(type_abbreviations + i + 1);
+
+ if (pg_strcasecmp(functoken, shortname) == 0)
+ {
+ appendPQExpBuffer(&buf, "LOWER('%s')::text\n", longname);
+ found_abbreviation = true;
+ break;
+ }
+ }
+ if (!found_abbreviation)
+ {
+ appendPQExpBuffer(&buf, "LOWER(%s)::text\n", PQescapeLiteral(pset.db, functoken, strlen(functoken)));
+ }
+
+ }
+ else if (functoken[0] == ')' && argoffset)
+ { /* Force limit the number of args */
+ appendPQExpBuffer(&buf, " AND p.pronargs = %d\n", argoffset);
+ break;
+ }
+ }
+ }
+
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index f0e3ec957c..a3400afb8c 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,7 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7204fdb0b4..15de4c9314 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5076,3 +5076,110 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- list specific functions of the same name but different args
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+-- With no arguments, all functions are shown
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | bit varying | func
+ public | mtest | integer | boolean, character, character varying | func
+ public | mtest | integer | double precision, double precision, integer | func
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+ public | mtest | integer | time without time zone, time with time zone | func
+(7 rows)
+
+-- An invalid argument type matches nothing
+\df mtest mint
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+(2 rows)
+
+-- Two argument types match up
+\df mtest integer text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+(1 row)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------+------
+ public | mtest | integer | boolean, character, character varying | func
+(1 row)
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | double precision, double precision, integer | func
+(1 row)
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (time timetz)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | time without time zone, time with time zone | func
+(1 row)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(1 row)
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | bit varying | func
+(1 row)
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 537d5332aa..3efac568d8 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1228,3 +1228,53 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+
+-- list specific functions of the same name but different args
+
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+
+-- With no arguments, all functions are shown
+\df mtest
+
+-- An invalid argument type matches nothing
+\df mtest mint
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+
+-- Two argument types match up
+\df mtest integer text
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (time timetz)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+
+\df mtest
On Thu, Dec 31, 2020 at 7:01 AM Greg Sabino Mullane <htamfids@gmail.com> wrote:
Attached is the latest patch against HEAD - basically fixes a few typos.
Hi Greg,
It looks like there is a collation dependency here that causes the
test to fail on some systems:
=== ./src/test/regress/regression.diffs ===
diff -U3 /tmp/cirrus-ci-build/src/test/regress/expected/psql.out
/tmp/cirrus-ci-build/src/test/regress/results/psql.out
--- /tmp/cirrus-ci-build/src/test/regress/expected/psql.out 2021-01-01
16:05:25.749692000 +0000
+++ /tmp/cirrus-ci-build/src/test/regress/results/psql.out 2021-01-01
16:11:28.525632000 +0000
@@ -5094,8 +5094,8 @@
public | mtest | integer | double precision, double precision, integer | func
public | mtest | integer | integer | func
public | mtest | integer | integer, text | func
- public | mtest | integer | timestamp without time zone, timestamp
with time zone | func
public | mtest | integer | time without time zone, time with time zone | func
+ public | mtest | integer | timestamp without time zone, timestamp
with time zone | func
On Sat, Jan 2, 2021 at 1:56 AM Thomas Munro <thomas.munro@gmail.com> wrote:
...
It looks like there is a collation dependency here that causes the
test to fail on some systems:
Thanks for pointing that out. I tweaked the function definitions to
hopefully sidestep the ordering issue - attached is v4.
Cheers,
Greg
Attachments:
v4-psql-df-pick-function-by-type.patchapplication/octet-stream; name=v4-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 221a967bfe..cf3e8c7134 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1574,7 +1574,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ] [ types ]</literal></term>
<listitem>
<para>
@@ -1587,6 +1587,7 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional words are considered type arguments to help narrow the list of returned functions.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1598,7 +1599,7 @@ testdb=>
<tip>
<para>
- To look up functions taking arguments or returning values of a specific
+ To look up functions returning values of a specific
data type, use your pager's search capability to scroll through the
<literal>\df</literal> output.
</para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 303e7c3ad8..d449cea66c 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -781,6 +781,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'f': /* function subsystem */
switch (cmd[2])
{
+ char *funcargs;
+
case '\0':
case '+':
case 'S':
@@ -789,7 +791,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
+ free(funcargs);
break;
default:
status = PSQL_CMD_UNKNOWN;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 52c6de51b6..70f76e02ef 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -26,6 +26,7 @@
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "settings.h"
+#include "stringutils.h"
#include "variables.h"
static bool describeOneTableDetails(const char *schemaname,
@@ -312,7 +313,7 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -626,6 +627,67 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
+ /*
+ * Check for any additional arguments to narrow down which functions are
+ * desired
+ */
+ if (funcargs)
+ {
+
+ bool is_initial_run = true;
+ bool found_abbreviation;
+ int argoffset = 0;
+ char *functoken;
+
+ static const char *type_abbreviations[] = {
+ "bool", "boolean", "bool[]", "boolean[]",
+ "char", "character", "char[]", "character[]",
+ "double", "double precision", "double[]", "double precision[]",
+ "float", "double precision", "float[]", "double precision[]",
+ "int", "integer", "int[]", "integer[]",
+ "time", "time without time zone", "time[]", "time without time zone[]",
+ "timetz", "time with time zone", "timetz[]", "time with time zone[]",
+ "timestamp", "timestamp without time zone", "timestamp[]", "timestamp without time zone[]",
+ "timestamptz", "timestamp with time zone", "timestamptz[]", "timestamp with time zone[]",
+ "varbit", "bit varying", "varbit[]", "bit varying[]",
+ "varchar", "character varying", "varchar[]", "character varying[]",
+ NULL
+ };
+
+ while ((functoken = strtokx(is_initial_run ? funcargs : NULL, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
+ {
+ is_initial_run = false;
+ found_abbreviation = false;
+
+ if (isalpha(functoken[0]))
+ {
+ appendPQExpBuffer(&buf, " AND p.proargtypes[%d]::regtype::text = ", argoffset++);
+ for (int i = 0; NULL != *(type_abbreviations + i); i += 2)
+ {
+ const char *shortname = *(type_abbreviations + i);
+ const char *longname = *(type_abbreviations + i + 1);
+
+ if (pg_strcasecmp(functoken, shortname) == 0)
+ {
+ appendPQExpBuffer(&buf, "LOWER('%s')::text\n", longname);
+ found_abbreviation = true;
+ break;
+ }
+ }
+ if (!found_abbreviation)
+ {
+ appendPQExpBuffer(&buf, "LOWER(%s)::text\n", PQescapeLiteral(pset.db, functoken, strlen(functoken)));
+ }
+
+ }
+ else if (functoken[0] == ')' && argoffset)
+ { /* Force limit the number of args */
+ appendPQExpBuffer(&buf, " AND p.pronargs = %d\n", argoffset);
+ break;
+ }
+ }
+ }
+
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 6044e3a082..26ae135d79 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,7 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7204fdb0b4..0ff670d5b5 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5076,3 +5076,110 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- list specific functions of the same name but different args
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+-- With no arguments, all functions are shown
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | bit varying | func
+ public | mtest | integer | boolean, character, character varying | func
+ public | mtest | integer | boolean, time without time zone, time with time zone | func
+ public | mtest | integer | double precision, double precision, integer | func
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(7 rows)
+
+-- An invalid argument type matches nothing
+\df mtest mint
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+(2 rows)
+
+-- Two argument types match up
+\df mtest integer text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+(1 row)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------+------
+ public | mtest | integer | boolean, character, character varying | func
+(1 row)
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------------+------
+ public | mtest | integer | double precision, double precision, integer | func
+(1 row)
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (bool time timetz)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+------------------------------------------------------+------
+ public | mtest | integer | boolean, time without time zone, time with time zone | func
+(1 row)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(1 row)
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | bit varying | func
+(1 row)
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(bool,time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 537d5332aa..2f475fe0c3 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1228,3 +1228,53 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+
+-- list specific functions of the same name but different args
+
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,int) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+
+-- With no arguments, all functions are shown
+\df mtest
+
+-- An invalid argument type matches nothing
+\df mtest mint
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+
+-- Two argument types match up
+\df mtest integer text
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int -> integer
+\df mtest double float int
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (bool time timetz)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,int);
+drop function mtest(bool,time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+
+\df mtest
Hi
I tried this patch out last year but was overrolled by Other Stuff before I got
around to providing any feedback, and was reminded of it just now when I was
trying to execute "\df somefunction text int" or similar, which had me
confused until I remembered it's not a feature yet, so it would
certainly be very
welcome to have this.
2020年11月3日(火) 23:27 Greg Sabino Mullane <htamfids@gmail.com>:
Thanks for looking this over!
some Abbreviations of common types are not added to the type_abbreviations[] Such as:
Int8 => bigint
I wasn't aiming to provide a canonical list, as I personally have never seen
anyone use int8 instead of bigint when (for example) creating a function, but
I'm not strongly opposed to expanding the list.
I have vague memories of working with "int8" a bit (possibly related to an
Informix migration), anyway it seems easy enough to add them for completeness
as someone (possibly migrating from another database) might wonder why
it's not working.
Just a small code readability suggestion - in exec_command_d(), it seems
neater to put the funcargs declaration in a block together with the
code with which uses it (see attached diff).
Regards
Ian Barwick
--
EnterpriseDB: https://www.enterprisedb.com
Attachments:
funcargs.difftext/x-patch; charset=US-ASCII; name=funcargs.diffDownload
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index d449cea66c..e8c1fd64bc 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -781,8 +781,6 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'f': /* function subsystem */
switch (cmd[2])
{
- char *funcargs;
-
case '\0':
case '+':
case 'S':
@@ -791,10 +789,14 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
+ {
+ char *funcargs;
+
funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
free(funcargs);
break;
+ }
default:
status = PSQL_CMD_UNKNOWN;
break;
Thanks for the feedback: new version v5 (attached) has int8, plus the
suggested code formatting.
Cheers,
Greg
Attachments:
v5-psql-df-pick-function-by-type.patchapplication/octet-stream; name=v5-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 221a967bfe..cf3e8c7134 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1574,7 +1574,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ] [ types ]</literal></term>
<listitem>
<para>
@@ -1587,6 +1587,7 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional words are considered type arguments to help narrow the list of returned functions.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1598,7 +1599,7 @@ testdb=>
<tip>
<para>
- To look up functions taking arguments or returning values of a specific
+ To look up functions returning values of a specific
data type, use your pager's search capability to scroll through the
<literal>\df</literal> output.
</para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 303e7c3ad8..84fd34580f 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -789,8 +789,15 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
- break;
+ {
+ char *funcargs;
+
+
+ funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
+ free(funcargs);
+ break;
+ }
default:
status = PSQL_CMD_UNKNOWN;
break;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index caf97563f4..6d85d9c1e9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -26,6 +26,7 @@
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "settings.h"
+#include "stringutils.h"
#include "variables.h"
static bool describeOneTableDetails(const char *schemaname,
@@ -312,7 +313,7 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -626,6 +627,68 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
+ /*
+ * Check for any additional arguments to narrow down which functions are
+ * desired
+ */
+ if (funcargs)
+ {
+
+ bool is_initial_run = true;
+ bool found_abbreviation;
+ int argoffset = 0;
+ char *functoken;
+
+ static const char *type_abbreviations[] = {
+ "bool", "boolean", "bool[]", "boolean[]",
+ "char", "character", "char[]", "character[]",
+ "double", "double precision", "double[]", "double precision[]",
+ "float", "double precision", "float[]", "double precision[]",
+ "int", "integer", "int[]", "integer[]",
+ "int8", "bigint", "int8[]", "bigint[]",
+ "time", "time without time zone", "time[]", "time without time zone[]",
+ "timetz", "time with time zone", "timetz[]", "time with time zone[]",
+ "timestamp", "timestamp without time zone", "timestamp[]", "timestamp without time zone[]",
+ "timestamptz", "timestamp with time zone", "timestamptz[]", "timestamp with time zone[]",
+ "varbit", "bit varying", "varbit[]", "bit varying[]",
+ "varchar", "character varying", "varchar[]", "character varying[]",
+ NULL
+ };
+
+ while ((functoken = strtokx(is_initial_run ? funcargs : NULL, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
+ {
+ is_initial_run = false;
+ found_abbreviation = false;
+
+ if (isalpha(functoken[0]))
+ {
+ appendPQExpBuffer(&buf, " AND p.proargtypes[%d]::regtype::text = ", argoffset++);
+ for (int i = 0; NULL != *(type_abbreviations + i); i += 2)
+ {
+ const char *shortname = *(type_abbreviations + i);
+ const char *longname = *(type_abbreviations + i + 1);
+
+ if (pg_strcasecmp(functoken, shortname) == 0)
+ {
+ appendPQExpBuffer(&buf, "LOWER('%s')::text\n", longname);
+ found_abbreviation = true;
+ break;
+ }
+ }
+ if (!found_abbreviation)
+ {
+ appendPQExpBuffer(&buf, "LOWER(%s)::text\n", PQescapeLiteral(pset.db, functoken, strlen(functoken)));
+ }
+
+ }
+ else if (functoken[0] == ')' && argoffset)
+ { /* Force limit the number of args */
+ appendPQExpBuffer(&buf, " AND p.pronargs = %d\n", argoffset);
+ break;
+ }
+ }
+ }
+
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 6044e3a082..26ae135d79 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,7 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7204fdb0b4..dd5e03c60e 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5076,3 +5076,118 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- list specific functions of the same name but different args
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,bigint) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+-- With no arguments, all functions are shown
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | bit varying | func
+ public | mtest | integer | boolean, character, character varying | func
+ public | mtest | integer | boolean, time without time zone, time with time zone | func
+ public | mtest | integer | double precision, double precision, bigint | func
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(7 rows)
+
+-- An invalid argument type matches nothing
+\df mtest mint
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+(2 rows)
+
+-- Two argument types match up
+\df mtest integer text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- int -> integer
+\df mtest int text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+(1 row)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------+------
+ public | mtest | integer | boolean, character, character varying | func
+(1 row)
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int8 -> bigint
+\df mtest double float int8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+--------------------------------------------+------
+ public | mtest | integer | double precision, double precision, bigint | func
+(1 row)
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (bool time timetz)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+------------------------------------------------------+------
+ public | mtest | integer | boolean, time without time zone, time with time zone | func
+(1 row)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(1 row)
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | bit varying | func
+(1 row)
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,bigint);
+drop function mtest(bool,time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 537d5332aa..8e36cd5a35 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1228,3 +1228,56 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+
+-- list specific functions of the same name but different args
+
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,bigint) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit) returns int as $$ select 1; $$ language sql;
+
+-- With no arguments, all functions are shown
+\df mtest
+
+-- An invalid argument type matches nothing
+\df mtest mint
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+
+-- Two argument types match up
+\df mtest integer text
+
+-- int -> integer
+\df mtest int text
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int8 -> bigint
+\df mtest double float int8
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (bool time timetz)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+
+-- Allowed abbreviation: varbit -> bit varying
+\df mtest varbit
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,bigint);
+drop function mtest(bool,time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit);
+
+\df mtest
2021年1月15日(金) 1:46 Greg Sabino Mullane <htamfids@gmail.com>:
Thanks for the feedback: new version v5 (attached) has int8, plus the
suggested code formatting.Cheers,
Greg
Thanks for the update.
In my preceding mail I meant we should add int2, int4 and int8 for
completeness
(apologies, I was a bit unclear there), as AFAICS that covers all aliases,
even if these
three are less widely used.
FWIW one place where these do get used in substantial numbers is in the
regression tests themselves:
$ for L in 2 4 8; do git grep int$L src/test/regress/ | wc -l; done
544
2332
1353
Regards
Ian Barwick
--
EnterpriseDB: https://www.enterprisedb.com
Ha ha ha, my bad, I am not sure why I left those out. Here is a new patch
with int2, int4, and int8. Thanks for the email.
Cheers,
Greg
Attachments:
v6-psql-df-pick-function-by-type.patchapplication/octet-stream; name=v6-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 221a967bfe..cf3e8c7134 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1574,7 +1574,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ] [ types ]</literal></term>
<listitem>
<para>
@@ -1587,6 +1587,7 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional words are considered type arguments to help narrow the list of returned functions.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1598,7 +1599,7 @@ testdb=>
<tip>
<para>
- To look up functions taking arguments or returning values of a specific
+ To look up functions returning values of a specific
data type, use your pager's search capability to scroll through the
<literal>\df</literal> output.
</para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 303e7c3ad8..84fd34580f 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -789,8 +789,15 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
- break;
+ {
+ char *funcargs;
+
+
+ funcargs = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true);
+ success = describeFunctions(&cmd[2], pattern, show_verbose, show_system, funcargs);
+ free(funcargs);
+ break;
+ }
default:
status = PSQL_CMD_UNKNOWN;
break;
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index caf97563f4..fd4aebd87e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -26,6 +26,7 @@
#include "fe_utils/print.h"
#include "fe_utils/string_utils.h"
#include "settings.h"
+#include "stringutils.h"
#include "variables.h"
static bool describeOneTableDetails(const char *schemaname,
@@ -312,7 +313,7 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -626,6 +627,70 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
+ /*
+ * Check for any additional arguments to narrow down which functions are
+ * desired
+ */
+ if (funcargs)
+ {
+
+ bool is_initial_run = true;
+ bool found_abbreviation;
+ int argoffset = 0;
+ char *functoken;
+
+ static const char *type_abbreviations[] = {
+ "bool", "boolean", "bool[]", "boolean[]",
+ "char", "character", "char[]", "character[]",
+ "double", "double precision", "double[]", "double precision[]",
+ "float", "double precision", "float[]", "double precision[]",
+ "int", "integer", "int[]", "integer[]",
+ "int2", "smallint", "int2[]", "smallint[]",
+ "int4", "integer", "int4[]", "integer[]",
+ "int8", "bigint", "int8[]", "bigint[]",
+ "time", "time without time zone", "time[]", "time without time zone[]",
+ "timetz", "time with time zone", "timetz[]", "time with time zone[]",
+ "timestamp", "timestamp without time zone", "timestamp[]", "timestamp without time zone[]",
+ "timestamptz", "timestamp with time zone", "timestamptz[]", "timestamp with time zone[]",
+ "varbit", "bit varying", "varbit[]", "bit varying[]",
+ "varchar", "character varying", "varchar[]", "character varying[]",
+ NULL
+ };
+
+ while ((functoken = strtokx(is_initial_run ? funcargs : NULL, " \t\n\r", ".,();", "\"", 0, false, true, pset.encoding)))
+ {
+ is_initial_run = false;
+ found_abbreviation = false;
+
+ if (isalpha(functoken[0]))
+ {
+ appendPQExpBuffer(&buf, " AND p.proargtypes[%d]::regtype::text = ", argoffset++);
+ for (int i = 0; NULL != *(type_abbreviations + i); i += 2)
+ {
+ const char *shortname = *(type_abbreviations + i);
+ const char *longname = *(type_abbreviations + i + 1);
+
+ if (pg_strcasecmp(functoken, shortname) == 0)
+ {
+ appendPQExpBuffer(&buf, "LOWER('%s')::text\n", longname);
+ found_abbreviation = true;
+ break;
+ }
+ }
+ if (!found_abbreviation)
+ {
+ appendPQExpBuffer(&buf, "LOWER(%s)::text\n", PQescapeLiteral(pset.db, functoken, strlen(functoken)));
+ }
+
+ }
+ else if (functoken[0] == ')' && argoffset)
+ { /* Force limit the number of args */
+ appendPQExpBuffer(&buf, " AND p.pronargs = %d\n", argoffset);
+ break;
+ }
+ }
+ }
+
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 6044e3a082..26ae135d79 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,7 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem, const char *funcargs);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7204fdb0b4..c0d7c2a2d7 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5076,3 +5076,118 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- list specific functions of the same name but different args
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,bigint) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit,int,smallint) returns int as $$ select 1; $$ language sql;
+-- With no arguments, all functions are shown
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | bit varying, integer, smallint | func
+ public | mtest | integer | boolean, character, character varying | func
+ public | mtest | integer | boolean, time without time zone, time with time zone | func
+ public | mtest | integer | double precision, double precision, bigint | func
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(7 rows)
+
+-- An invalid argument type matches nothing
+\df mtest mint
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+ public | mtest | integer | integer, text | func
+(2 rows)
+
+-- Two argument types match up
+\df mtest integer text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- int -> integer
+\df mtest int text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer, text | func
+(1 row)
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------+------
+ public | mtest | integer | integer | func
+(1 row)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+---------------------------------------+------
+ public | mtest | integer | boolean, character, character varying | func
+(1 row)
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int8 -> bigint
+\df mtest double float int8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+--------------------------------------------+------
+ public | mtest | integer | double precision, double precision, bigint | func
+(1 row)
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (bool time timetz)
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+------------------------------------------------------+------
+ public | mtest | integer | boolean, time without time zone, time with time zone | func
+(1 row)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+-------------------------------------------------------+------
+ public | mtest | integer | timestamp without time zone, timestamp with time zone | func
+(1 row)
+
+-- Allowed abbreviations: varbit -> bit varying, int4 -> integer, int2 -> smallint
+\df mtest varbit int4 int2
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+-------+------------------+--------------------------------+------
+ public | mtest | integer | bit varying, integer, smallint | func
+(1 row)
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,bigint);
+drop function mtest(bool,time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit,int,smallint);
+\df mtest
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 537d5332aa..d33ab3a625 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1228,3 +1228,56 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+
+-- list specific functions of the same name but different args
+
+create function mtest(int) returns int as $$ select 1; $$ language sql;
+create function mtest(int,text) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,character(10),varchar(10)) returns int as $$ select 1; $$ language sql;
+create function mtest(float,float,bigint) returns int as $$ select 1; $$ language sql;
+create function mtest(bool,time,timetz) returns int as $$ select 1; $$ language sql;
+create function mtest(timestamp,timestamptz) returns int as $$ select 1; $$ language sql;
+create function mtest(varbit,int,smallint) returns int as $$ select 1; $$ language sql;
+
+-- With no arguments, all functions are shown
+\df mtest
+
+-- An invalid argument type matches nothing
+\df mtest mint
+
+-- A single argument type matches all functions starting with that type
+\df mtest integer
+
+-- Two argument types match up
+\df mtest integer text
+
+-- int -> integer
+\df mtest int text
+
+-- A single argument type only matches a single argument if a closing paren is added
+\df mtest (integer)
+
+-- Allowed abbreviations: bool->boolean, char -> character, varchar -> character varying
+\df mtest bool,char,varchar
+
+-- Allowed abbreviations: double -> double precision, float - double precision, int8 -> bigint
+\df mtest double float int8
+
+-- Allowed abbreviations: time -> time without time zone, timetz -> time with time zone
+\df mtest (bool time timetz)
+
+-- Allowed abbreviations: timestamp -> timestamp without time zone, timestamptz -> timestamp with time zone
+\df mtest timestamp timestamptz
+
+-- Allowed abbreviations: varbit -> bit varying, int4 -> integer, int2 -> smallint
+\df mtest varbit int4 int2
+
+drop function mtest(int);
+drop function mtest(int,text);
+drop function mtest(bool,char,varchar);
+drop function mtest(float,float,bigint);
+drop function mtest(bool,time,timetz);
+drop function mtest(timestamp,timestamptz);
+drop function mtest(varbit,int,smallint);
+
+\df mtest
On 1/19/21 11:58 AM, Greg Sabino Mullane wrote:
Ha ha ha, my bad, I am not sure why I left those out. Here is a new
patch with int2, int4, and int8. Thanks for the email.
Ian, does the new patch look good to you?
Also, not sure why the target version for this patch is stable so I have
updated it to PG14.
Regards,
--
-David
david@pgmasters.net
Greg Sabino Mullane <htamfids@gmail.com> writes:
[ v6-psql-df-pick-function-by-type.patch ]
I looked this over. I like the idea a lot, but not much of anything
about the implementation. I think the additional arguments should be
matched to the function types using the same rules as for \dT. That
allows patterns for the argument type names, which is particularly
useful if you want to do something like
\df foo * integer
to find functions whose second argument is integer, without restricting
the first argument.
As a lesser quibble, splitting the arguments with strtokx is a hack;
we should let the normal psql scanner collect the arguments.
So that leads me to the attached, which I think is committable. Since
we're down to the last day of the CF, I'm going to push this shortly if
there aren't squawks soon.
regards, tom lane
Attachments:
v7-psql-df-pick-function-by-type.patchtext/x-diff; charset=us-ascii; name=v7-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index c1451c1672..db987c14bb 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1567,7 +1567,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> [ <replaceable class="parameter">arg_pattern</replaceable> ... ] ]</literal></term>
<listitem>
<para>
@@ -1580,6 +1580,9 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional arguments are type-name patterns, which are matched
+ to the type names of the first, second, and so on arguments of the
+ function.
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1589,14 +1592,6 @@ testdb=>
language, source code and description.
</para>
- <tip>
- <para>
- To look up functions taking arguments or returning values of a specific
- data type, use your pager's search capability to scroll through the
- <literal>\df</literal> output.
- </para>
- </tip>
-
</listitem>
</varlistentry>
@@ -4986,6 +4981,22 @@ second | four
</programlisting>
</para>
+ <para>
+ Here is an example of using the <command>\df</command> command to
+ find only functions with names matching <literal>int*pl</literal>
+ and whose second argument is of type <type>bigint</type>:
+<programlisting>
+testdb=> <userinput>\df int*pl * bigint</userinput>
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+</programlisting>
+ </para>
+
<para>
When suitable, query results can be shown in a crosstab representation
with the <command>\crosstabview</command> command:
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index e04ccc5b62..8d2c8e85df 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -72,6 +72,9 @@ static backslashResult exec_command_copyright(PsqlScanState scan_state, bool act
static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
const char *cmd);
+static bool exec_command_df(PsqlScanState scan_state, const char *cmd,
+ const char *func_pattern,
+ bool show_verbose, bool show_system);
static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf);
static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
@@ -790,7 +793,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ success = exec_command_df(scan_state, cmd, pattern,
+ show_verbose, show_system);
break;
default:
status = PSQL_CMD_UNKNOWN;
@@ -951,6 +955,40 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
return status;
}
+/* \df and related commands; messy enough to split out of exec_command_d */
+static bool
+exec_command_df(PsqlScanState scan_state, const char *cmd,
+ const char *func_pattern,
+ bool show_verbose, bool show_system)
+{
+ bool success;
+ char *arg_patterns[FUNC_MAX_ARGS];
+ int num_arg_patterns = 0;
+
+ /* Collect argument-type patterns too */
+ if (func_pattern) /* otherwise it was just \df */
+ {
+ char *ap;
+
+ while ((ap = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, true)) != NULL)
+ {
+ arg_patterns[num_arg_patterns++] = ap;
+ if (num_arg_patterns >= FUNC_MAX_ARGS)
+ break; /* protect limited-size array */
+ }
+ }
+
+ success = describeFunctions(&cmd[2], func_pattern,
+ arg_patterns, num_arg_patterns,
+ show_verbose, show_system);
+
+ while (--num_arg_patterns >= 0)
+ free(arg_patterns[num_arg_patterns]);
+
+ return success;
+}
+
/*
* \e or \edit -- edit the current query buffer, or edit a file and
* make it the query buffer
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 440249ff69..d31cdac3a7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -312,7 +312,9 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *func_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -517,6 +519,14 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"\nFROM pg_catalog.pg_proc p"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n");
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ appendPQExpBuffer(&buf,
+ " LEFT JOIN pg_catalog.pg_type t%d ON t%d.oid = p.proargtypes[%d]\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt%d ON nt%d.oid = t%d.typnamespace\n",
+ i, i, i, i, i, i);
+ }
+
if (verbose)
appendPQExpBufferStr(&buf,
" LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang\n");
@@ -622,11 +632,32 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, pattern, have_where, false,
+ processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
- if (!showSystem && !pattern)
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ /*
+ * Match type-name patterns against either internal or external name,
+ * like \dT. Unlike \dT, there seems no reason to discriminate
+ * against arrays or composite types.
+ */
+ char nspname[64];
+ char typname[64];
+ char ft[64];
+ char tiv[64];
+
+ snprintf(nspname, sizeof(nspname), "tn%d.nspname", i);
+ snprintf(typname, sizeof(typname), "t%d.typname", i);
+ snprintf(ft, sizeof(ft), "pg_catalog.format_type(t%d.oid, NULL)", i);
+ snprintf(tiv, sizeof(tiv), "pg_catalog.pg_type_is_visible(t%d.oid)", i);
+ processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ true, false,
+ nspname, typname, ft, tiv);
+ }
+
+ if (!showSystem && !func_pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 39856a0c7e..01deed6799 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,7 +19,9 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *func_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ac9a89a889..46e068cd7c 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -166,7 +166,7 @@ slashUsage(unsigned short int pager)
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
- output = PageOutput(133, pager ? &(pset.popt.topt) : NULL);
+ output = PageOutput(135, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
@@ -240,7 +240,8 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\des[+] [PATTERN] list foreign servers\n"));
fprintf(output, _(" \\deu[+] [PATTERN] list user mappings\n"));
fprintf(output, _(" \\dew[+] [PATTERN] list foreign-data wrappers\n"));
- fprintf(output, _(" \\df[anptw][S+] [PATRN] list [only agg/normal/procedures/trigger/window] functions\n"));
+ fprintf(output, _(" \\df[anptw][S+] [FUNCPTRN [TYPEPTRN ...]]\n"));
+ fprintf(output, _(" list [only agg/normal/procedure/trigger/window] functions\n"));
fprintf(output, _(" \\dF[+] [PATTERN] list text search configurations\n"));
fprintf(output, _(" \\dFd[+] [PATTERN] list text search dictionaries\n"));
fprintf(output, _(" \\dFp[+] [PATTERN] list text search parsers\n"));
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 9a1ea9ab98..5b206c7481 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -1062,10 +1062,16 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* regexp errors. Outside quotes, however, let them pass through
* as-is; this lets knowledgeable users build regexp expressions
* that are more powerful than shell-style patterns.
+ *
+ * As an exception to that, though, always quote "[]", as that's
+ * much more likely to be an attempt to write an array type name
+ * than it is to be the start of a regexp bracket expression.
*/
if ((inquotes || force_escape) &&
strchr("|*+?()[]{}.^$\\", ch))
appendPQExpBufferChar(curbuf, '\\');
+ else if (ch == '[' && cp[1] == ']')
+ appendPQExpBufferChar(curbuf, '\\');
i = PQmblen(cp, encoding);
while (i-- && *cp)
{
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 9a51940530..3135b2ab4b 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5078,6 +5078,78 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- check \df with argument specifications
+\df *sqrt
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | dsqrt | double precision | double precision | func
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | double precision | double precision | func
+ pg_catalog | sqrt | numeric | numeric | func
+(4 rows)
+
+\df *sqrt num*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | numeric | numeric | func
+(2 rows)
+
+\df int*pl
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+---------------------+------
+ pg_catalog | int24pl | integer | smallint, integer | func
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int2pl | smallint | smallint, smallint | func
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+ pg_catalog | int82pl | bigint | bigint, smallint | func
+ pg_catalog | int84pl | bigint | bigint, integer | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+ pg_catalog | interval_pl | interval | interval, interval | func
+(10 rows)
+
+\df int*pl int4
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+(3 rows)
+
+\df int*pl * int8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+
+\df acl* aclitem[]
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+----------------------------------------------------------------------------------------------------+------
+ pg_catalog | aclcontains | boolean | aclitem[], aclitem | func
+ pg_catalog | aclexplode | SETOF record | acl aclitem[], OUT grantor oid, OUT grantee oid, OUT privilege_type text, OUT is_grantable boolean | func
+ pg_catalog | aclinsert | aclitem[] | aclitem[], aclitem | func
+ pg_catalog | aclremove | aclitem[] | aclitem[], aclitem | func
+(4 rows)
+
+\dfa bit* small*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | bit_and | smallint | smallint | agg
+ pg_catalog | bit_or | smallint | smallint | agg
+ pg_catalog | bit_xor | smallint | smallint | agg
+(3 rows)
+
--
-- combined queries
--
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index bf06bb87b5..06b470aa8d 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1229,6 +1229,15 @@ drop role regress_partitioning_role;
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+-- check \df with argument specifications
+\df *sqrt
+\df *sqrt num*
+\df int*pl
+\df int*pl int4
+\df int*pl * int8
+\df acl* aclitem[]
+\dfa bit* small*
+
--
-- combined queries
--
I like the wildcard aspect, but I have a few issues with the patch:
* It doesn't respect some common abbreviations that work elsewhere (e.g.
CREATE FUNCTION). So while "int4" works, "int" does not. Nor does "float",
which thus requires the mandatory-double-quoted "double precision"
* Adding commas to the args, as returned by psql itself via \df, provides
no matches.
* There seems to be no way (?) to limit the functions returned if they
share a common root. The previous incantation allowed you to pull out
foo(int) from foo(int, bigint). This was a big motivation for writing this
patch.
* SQL error on \df foo a..b as well as one on \df foo (bigint bigint)
Cheers,
Greg
Greg Sabino Mullane <htamfids@gmail.com> writes:
I like the wildcard aspect, but I have a few issues with the patch:
* It doesn't respect some common abbreviations that work elsewhere (e.g.
CREATE FUNCTION). So while "int4" works, "int" does not. Nor does "float",
which thus requires the mandatory-double-quoted "double precision"
"\dT int" doesn't match anything either. Maybe there's room to improve
on that, but I don't think this patch should deviate from what \dT does.
* Adding commas to the args, as returned by psql itself via \df, provides
no matches.
The docs are fairly clear that the args are to be space-separated, not
comma-separated. This fits with psql's general treatment of backslash
arguments, and I think trying to "improve" on it will just end badly.
* There seems to be no way (?) to limit the functions returned if they
share a common root. The previous incantation allowed you to pull out
foo(int) from foo(int, bigint). This was a big motivation for writing this
patch.
Hmm, are you trying to say that a invocation with N arg patterns should
match only functions with exactly N arguments? We could do that, but
I'm not convinced it's an improvement over what I did here. Default
arguments are a counterexample.
* SQL error on \df foo a..b as well as one on \df foo (bigint bigint)
The first one seems to be a bug, will look. As for the second, I still
don't agree that that should be within the mandated syntax.
regards, tom lane
I wrote:
Greg Sabino Mullane <htamfids@gmail.com> writes:
* SQL error on \df foo a..b as well as one on \df foo (bigint bigint)
The first one seems to be a bug, will look.
Argh, silly typo (and I'd failed to test the schema-qualified-name case).
While I was thinking about use-cases for this, I realized that at least
for me, being able to restrict \do operator searches by input type would
be even more useful than is true for \df. Operator names tend to be
overloaded even more heavily than functions. So here's a v8 that
also fixes \do in the same spirit.
(With respect to the other point: for \do it does seem to make sense
to constrain the match to operators with exactly as many arguments
as specified. I still say that's a bad idea for functions, though.)
regards, tom lane
Attachments:
v8-psql-df-pick-function-by-type.patchtext/x-diff; charset=us-ascii; name=v8-psql-df-pick-function-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index c1451c1672..d54e8b2457 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1567,7 +1567,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> [ <replaceable class="parameter">arg_pattern</replaceable> ... ] ]</literal></term>
<listitem>
<para>
@@ -1580,6 +1580,9 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional arguments are type-name patterns, which are matched
+ to the type names of the first, second, and so on arguments of the
+ function. (Not all the function's arguments need be mentioned.)
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1589,14 +1592,6 @@ testdb=>
language, source code and description.
</para>
- <tip>
- <para>
- To look up functions taking arguments or returning values of a specific
- data type, use your pager's search capability to scroll through the
- <literal>\df</literal> output.
- </para>
- </tip>
-
</listitem>
</varlistentry>
@@ -1721,12 +1716,18 @@ testdb=>
<varlistentry>
- <term><literal>\do[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\do[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> [ <replaceable class="parameter">arg_pattern</replaceable> [ <replaceable class="parameter">arg_pattern</replaceable> ] ] ]</literal></term>
<listitem>
<para>
Lists operators with their operand and result types.
If <replaceable class="parameter">pattern</replaceable> is
specified, only operators whose names match the pattern are listed.
+ If one <replaceable class="parameter">arg_pattern</replaceable> is
+ specified, only prefix operators whose right argument's type name
+ matches that pattern are listed.
+ If two <replaceable class="parameter">arg_pattern</replaceable>s
+ are specified, only infix operators whose argument type names match
+ those patterns are listed.
By default, only user-created objects are shown; supply a
pattern or the <literal>S</literal> modifier to include system
objects.
@@ -4986,6 +4987,22 @@ second | four
</programlisting>
</para>
+ <para>
+ Here is an example of using the <command>\df</command> command to
+ find only functions with names matching <literal>int*pl</literal>
+ and whose second argument is of type <type>bigint</type>:
+<programlisting>
+testdb=> <userinput>\df int*pl * bigint</userinput>
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+</programlisting>
+ </para>
+
<para>
When suitable, query results can be shown in a crosstab representation
with the <command>\crosstabview</command> command:
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index e04ccc5b62..543401c6d6 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -72,6 +72,9 @@ static backslashResult exec_command_copyright(PsqlScanState scan_state, bool act
static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
const char *cmd);
+static bool exec_command_dfo(PsqlScanState scan_state, const char *cmd,
+ const char *pattern,
+ bool show_verbose, bool show_system);
static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf);
static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
@@ -790,7 +793,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ success = exec_command_dfo(scan_state, cmd, pattern,
+ show_verbose, show_system);
break;
default:
status = PSQL_CMD_UNKNOWN;
@@ -811,7 +815,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
success = listSchemas(pattern, show_verbose, show_system);
break;
case 'o':
- success = describeOperators(pattern, show_verbose, show_system);
+ success = exec_command_dfo(scan_state, cmd, pattern,
+ show_verbose, show_system);
break;
case 'O':
success = listCollations(pattern, show_verbose, show_system);
@@ -951,6 +956,45 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
return status;
}
+/* \df and \do; messy enough to split out of exec_command_d */
+static bool
+exec_command_dfo(PsqlScanState scan_state, const char *cmd,
+ const char *pattern,
+ bool show_verbose, bool show_system)
+{
+ bool success;
+ char *arg_patterns[FUNC_MAX_ARGS];
+ int num_arg_patterns = 0;
+
+ /* Collect argument-type patterns too */
+ if (pattern) /* otherwise it was just \df or \do */
+ {
+ char *ap;
+
+ while ((ap = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, true)) != NULL)
+ {
+ arg_patterns[num_arg_patterns++] = ap;
+ if (num_arg_patterns >= FUNC_MAX_ARGS)
+ break; /* protect limited-size array */
+ }
+ }
+
+ if (cmd[1] == 'f')
+ success = describeFunctions(&cmd[2], pattern,
+ arg_patterns, num_arg_patterns,
+ show_verbose, show_system);
+ else
+ success = describeOperators(pattern,
+ arg_patterns, num_arg_patterns,
+ show_verbose, show_system);
+
+ while (--num_arg_patterns >= 0)
+ free(arg_patterns[num_arg_patterns]);
+
+ return success;
+}
+
/*
* \e or \edit -- edit the current query buffer, or edit a file and
* make it the query buffer
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 52f7b2ce78..06ef4b26ea 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -312,7 +312,9 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *func_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -524,6 +526,14 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"\nFROM pg_catalog.pg_proc p"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n");
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ appendPQExpBuffer(&buf,
+ " LEFT JOIN pg_catalog.pg_type t%d ON t%d.oid = p.proargtypes[%d]\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt%d ON nt%d.oid = t%d.typnamespace\n",
+ i, i, i, i, i, i);
+ }
+
if (verbose)
appendPQExpBufferStr(&buf,
" LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang\n");
@@ -629,11 +639,32 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, pattern, have_where, false,
+ processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
- if (!showSystem && !pattern)
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ /*
+ * Match type-name patterns against either internal or external name,
+ * like \dT. Unlike \dT, there seems no reason to discriminate
+ * against arrays or composite types.
+ */
+ char nspname[64];
+ char typname[64];
+ char ft[64];
+ char tiv[64];
+
+ snprintf(nspname, sizeof(nspname), "nt%d.nspname", i);
+ snprintf(typname, sizeof(typname), "t%d.typname", i);
+ snprintf(ft, sizeof(ft), "pg_catalog.format_type(t%d.oid, NULL)", i);
+ snprintf(tiv, sizeof(tiv), "pg_catalog.pg_type_is_visible(t%d.oid)", i);
+ processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ true, false,
+ nspname, typname, ft, tiv);
+ }
+
+ if (!showSystem && !func_pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
@@ -787,7 +818,9 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
* Describe operators
*/
bool
-describeOperators(const char *pattern, bool verbose, bool showSystem)
+describeOperators(const char *oper_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem)
{
PQExpBufferData buf;
PGresult *res;
@@ -836,14 +869,55 @@ describeOperators(const char *pattern, bool verbose, bool showSystem)
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n",
gettext_noop("Description"));
- if (!showSystem && !pattern)
+ if (num_arg_patterns >= 2)
+ {
+ num_arg_patterns = 2; /* ignore any additional arguments */
+ appendPQExpBufferStr(&buf,
+ " LEFT JOIN pg_catalog.pg_type t0 ON t0.oid = o.oprleft\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n"
+ " LEFT JOIN pg_catalog.pg_type t1 ON t1.oid = o.oprright\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt1 ON nt1.oid = t1.typnamespace\n");
+ }
+ else if (num_arg_patterns == 1)
+ {
+ appendPQExpBufferStr(&buf,
+ " LEFT JOIN pg_catalog.pg_type t0 ON t0.oid = o.oprright\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n");
+ }
+
+ if (!showSystem && !oper_pattern)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, true,
+ processSQLNamePattern(pset.db, &buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
"n.nspname", "o.oprname", NULL,
"pg_catalog.pg_operator_is_visible(o.oid)");
+ if (num_arg_patterns == 1)
+ appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
+
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ /*
+ * Match type-name patterns against either internal or external name,
+ * like \dT. Unlike \dT, there seems no reason to discriminate
+ * against arrays or composite types.
+ */
+ char nspname[64];
+ char typname[64];
+ char ft[64];
+ char tiv[64];
+
+ snprintf(nspname, sizeof(nspname), "nt%d.nspname", i);
+ snprintf(typname, sizeof(typname), "t%d.typname", i);
+ snprintf(ft, sizeof(ft), "pg_catalog.format_type(t%d.oid, NULL)", i);
+ snprintf(tiv, sizeof(tiv), "pg_catalog.pg_type_is_visible(t%d.oid)", i);
+ processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ true, false,
+ nspname, typname, ft, tiv);
+ }
+
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3, 4;");
res = PSQLexec(buf.data);
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 39856a0c7e..71b320f1fc 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,13 +19,17 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *func_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
/* \do */
-extern bool describeOperators(const char *pattern, bool verbose, bool showSystem);
+extern bool describeOperators(const char *oper_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem);
/* \du, \dg */
extern bool describeRoles(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ac9a89a889..36501d5e2b 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -166,7 +166,7 @@ slashUsage(unsigned short int pager)
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
- output = PageOutput(133, pager ? &(pset.popt.topt) : NULL);
+ output = PageOutput(135, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
@@ -240,7 +240,8 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\des[+] [PATTERN] list foreign servers\n"));
fprintf(output, _(" \\deu[+] [PATTERN] list user mappings\n"));
fprintf(output, _(" \\dew[+] [PATTERN] list foreign-data wrappers\n"));
- fprintf(output, _(" \\df[anptw][S+] [PATRN] list [only agg/normal/procedures/trigger/window] functions\n"));
+ fprintf(output, _(" \\df[anptw][S+] [FUNCPTRN [TYPEPTRN ...]]\n"));
+ fprintf(output, _(" list [only agg/normal/procedure/trigger/window] functions\n"));
fprintf(output, _(" \\dF[+] [PATTERN] list text search configurations\n"));
fprintf(output, _(" \\dFd[+] [PATTERN] list text search dictionaries\n"));
fprintf(output, _(" \\dFp[+] [PATTERN] list text search parsers\n"));
@@ -251,7 +252,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dL[S+] [PATTERN] list procedural languages\n"));
fprintf(output, _(" \\dm[S+] [PATTERN] list materialized views\n"));
fprintf(output, _(" \\dn[S+] [PATTERN] list schemas\n"));
- fprintf(output, _(" \\do[S] [PATTERN] list operators\n"));
+ fprintf(output, _(" \\do[S] [OPPTRN [TYPEPTRN [TYPEPTRN]]] list operators\n"));
fprintf(output, _(" \\dO[S+] [PATTERN] list collations\n"));
fprintf(output, _(" \\dp [PATTERN] list table, view, and sequence access privileges\n"));
fprintf(output, _(" \\dP[itn+] [PATTERN] list [only index/table] partitioned relations [n=nested]\n"));
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 9a1ea9ab98..5b206c7481 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -1062,10 +1062,16 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* regexp errors. Outside quotes, however, let them pass through
* as-is; this lets knowledgeable users build regexp expressions
* that are more powerful than shell-style patterns.
+ *
+ * As an exception to that, though, always quote "[]", as that's
+ * much more likely to be an attempt to write an array type name
+ * than it is to be the start of a regexp bracket expression.
*/
if ((inquotes || force_escape) &&
strchr("|*+?()[]{}.^$\\", ch))
appendPQExpBufferChar(curbuf, '\\');
+ else if (ch == '[' && cp[1] == ']')
+ appendPQExpBufferChar(curbuf, '\\');
i = PQmblen(cp, encoding);
while (i-- && *cp)
{
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 9a51940530..c63be9e927 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5078,6 +5078,92 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- check \df, \do with argument specifications
+\df *sqrt
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | dsqrt | double precision | double precision | func
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | double precision | double precision | func
+ pg_catalog | sqrt | numeric | numeric | func
+(4 rows)
+
+\df *sqrt num*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | numeric | numeric | func
+(2 rows)
+
+\df int*pl
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+---------------------+------
+ pg_catalog | int24pl | integer | smallint, integer | func
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int2pl | smallint | smallint, smallint | func
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+ pg_catalog | int82pl | bigint | bigint, smallint | func
+ pg_catalog | int84pl | bigint | bigint, integer | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+ pg_catalog | interval_pl | interval | interval, interval | func
+(10 rows)
+
+\df int*pl int4
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+(3 rows)
+
+\df int*pl * pg_catalog.int8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+
+\df acl* aclitem[]
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+----------------------------------------------------------------------------------------------------+------
+ pg_catalog | aclcontains | boolean | aclitem[], aclitem | func
+ pg_catalog | aclexplode | SETOF record | acl aclitem[], OUT grantor oid, OUT grantee oid, OUT privilege_type text, OUT is_grantable boolean | func
+ pg_catalog | aclinsert | aclitem[] | aclitem[], aclitem | func
+ pg_catalog | aclremove | aclitem[] | aclitem[], aclitem | func
+(4 rows)
+
+\dfa bit* small*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | bit_and | smallint | smallint | agg
+ pg_catalog | bit_or | smallint | smallint | agg
+ pg_catalog | bit_xor | smallint | smallint | agg
+(3 rows)
+
+\do - int4
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+------------+------+---------------+----------------+-------------+-------------
+ pg_catalog | - | | integer | integer | negate
+(1 row)
+
+\do && anyarray *
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+------------+------+---------------+----------------+-------------+-------------
+ pg_catalog | && | anyarray | anyarray | boolean | overlaps
+(1 row)
+
--
-- combined queries
--
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index bf06bb87b5..9e897e18d8 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1229,6 +1229,17 @@ drop role regress_partitioning_role;
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+-- check \df, \do with argument specifications
+\df *sqrt
+\df *sqrt num*
+\df int*pl
+\df int*pl int4
+\df int*pl * pg_catalog.int8
+\df acl* aclitem[]
+\dfa bit* small*
+\do - int4
+\do && anyarray *
+
--
-- combined queries
--
I wrote:
Greg Sabino Mullane <htamfids@gmail.com> writes:
* There seems to be no way (?) to limit the functions returned if they
share a common root. The previous incantation allowed you to pull out
foo(int) from foo(int, bigint). This was a big motivation for writing this
patch.
Hmm, are you trying to say that a invocation with N arg patterns should
match only functions with exactly N arguments? We could do that, but
I'm not convinced it's an improvement over what I did here. Default
arguments are a counterexample.
I had an idea about that. I've not tested this, but I think it would be
a trivial matter of adding a coalesce() call to make the query act like
the type name for a not-present argument is an empty string, rather than
NULL which is what it gets right now. Then you could do what I think
you're asking for with
\df foo integer ""
Admittedly this is a bit of a hack, but to me this seems like a
minority use-case, so maybe that's good enough.
As for the point about "int" versus "integer" and so on, I wouldn't
be averse to installing a mapping layer for that, so long as we
did it to \dT as well.
regards, tom lane
I wrote:
I had an idea about that. I've not tested this, but I think it would be
a trivial matter of adding a coalesce() call to make the query act like
the type name for a not-present argument is an empty string, rather than
NULL which is what it gets right now. Then you could do what I think
you're asking for with
\df foo integer ""
Actually, what would make more sense is to treat "-" as specifying
a non-existent argument. There are precedents for that in, eg, \c,
and a dash is a little more robust than an empty-string argument.
So that leads me to 0001 attached.
As for the point about "int" versus "integer" and so on, I wouldn't
be averse to installing a mapping layer for that, so long as we
did it to \dT as well.
And for that, I suggest 0002. (We only need mappings for cases that
don't work out-of-the-box, so your list seemed a bit redundant.)
regards, tom lane
Attachments:
v9-0001-pick-functions-by-type.patchtext/x-diff; charset=us-ascii; name=v9-0001-pick-functions-by-type.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index c1451c1672..ddb7043362 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1567,7 +1567,7 @@ testdb=>
<varlistentry>
- <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\df[anptwS+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> [ <replaceable class="parameter">arg_pattern</replaceable> ... ] ]</literal></term>
<listitem>
<para>
@@ -1580,6 +1580,11 @@ testdb=>
If <replaceable
class="parameter">pattern</replaceable> is specified, only
functions whose names match the pattern are shown.
+ Any additional arguments are type-name patterns, which are matched
+ to the type names of the first, second, and so on arguments of the
+ function. (Matching functions can have more arguments than what
+ you specify. To prevent that, write a dash <literal>-</literal> as
+ the last <replaceable class="parameter">arg_pattern</replaceable>.)
By default, only user-created
objects are shown; supply a pattern or the <literal>S</literal>
modifier to include system objects.
@@ -1589,14 +1594,6 @@ testdb=>
language, source code and description.
</para>
- <tip>
- <para>
- To look up functions taking arguments or returning values of a specific
- data type, use your pager's search capability to scroll through the
- <literal>\df</literal> output.
- </para>
- </tip>
-
</listitem>
</varlistentry>
@@ -1721,12 +1718,19 @@ testdb=>
<varlistentry>
- <term><literal>\do[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term>
+ <term><literal>\do[S+] [ <link linkend="app-psql-patterns"><replaceable class="parameter">pattern</replaceable></link> [ <replaceable class="parameter">arg_pattern</replaceable> [ <replaceable class="parameter">arg_pattern</replaceable> ] ] ]</literal></term>
<listitem>
<para>
Lists operators with their operand and result types.
If <replaceable class="parameter">pattern</replaceable> is
specified, only operators whose names match the pattern are listed.
+ If one <replaceable class="parameter">arg_pattern</replaceable> is
+ specified, only prefix operators whose right argument's type name
+ matches that pattern are listed.
+ If two <replaceable class="parameter">arg_pattern</replaceable>s
+ are specified, only binary operators whose argument type names match
+ those patterns are listed. (Alternatively, write <literal>-</literal>
+ for the unused argument of a unary operator.)
By default, only user-created objects are shown; supply a
pattern or the <literal>S</literal> modifier to include system
objects.
@@ -4986,6 +4990,22 @@ second | four
</programlisting>
</para>
+ <para>
+ Here is an example of using the <command>\df</command> command to
+ find only functions with names matching <literal>int*pl</literal>
+ and whose second argument is of type <type>bigint</type>:
+<programlisting>
+testdb=> <userinput>\df int*pl * bigint</userinput>
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+</programlisting>
+ </para>
+
<para>
When suitable, query results can be shown in a crosstab representation
with the <command>\crosstabview</command> command:
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index e04ccc5b62..543401c6d6 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -72,6 +72,9 @@ static backslashResult exec_command_copyright(PsqlScanState scan_state, bool act
static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
const char *cmd);
+static bool exec_command_dfo(PsqlScanState scan_state, const char *cmd,
+ const char *pattern,
+ bool show_verbose, bool show_system);
static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
PQExpBuffer query_buf, PQExpBuffer previous_buf);
static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
@@ -790,7 +793,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'p':
case 't':
case 'w':
- success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
+ success = exec_command_dfo(scan_state, cmd, pattern,
+ show_verbose, show_system);
break;
default:
status = PSQL_CMD_UNKNOWN;
@@ -811,7 +815,8 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
success = listSchemas(pattern, show_verbose, show_system);
break;
case 'o':
- success = describeOperators(pattern, show_verbose, show_system);
+ success = exec_command_dfo(scan_state, cmd, pattern,
+ show_verbose, show_system);
break;
case 'O':
success = listCollations(pattern, show_verbose, show_system);
@@ -951,6 +956,45 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
return status;
}
+/* \df and \do; messy enough to split out of exec_command_d */
+static bool
+exec_command_dfo(PsqlScanState scan_state, const char *cmd,
+ const char *pattern,
+ bool show_verbose, bool show_system)
+{
+ bool success;
+ char *arg_patterns[FUNC_MAX_ARGS];
+ int num_arg_patterns = 0;
+
+ /* Collect argument-type patterns too */
+ if (pattern) /* otherwise it was just \df or \do */
+ {
+ char *ap;
+
+ while ((ap = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, true)) != NULL)
+ {
+ arg_patterns[num_arg_patterns++] = ap;
+ if (num_arg_patterns >= FUNC_MAX_ARGS)
+ break; /* protect limited-size array */
+ }
+ }
+
+ if (cmd[1] == 'f')
+ success = describeFunctions(&cmd[2], pattern,
+ arg_patterns, num_arg_patterns,
+ show_verbose, show_system);
+ else
+ success = describeOperators(pattern,
+ arg_patterns, num_arg_patterns,
+ show_verbose, show_system);
+
+ while (--num_arg_patterns >= 0)
+ free(arg_patterns[num_arg_patterns]);
+
+ return success;
+}
+
/*
* \e or \edit -- edit the current query buffer, or edit a file and
* make it the query buffer
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 52f7b2ce78..cd829e0759 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -312,7 +312,9 @@ describeTablespaces(const char *pattern, bool verbose)
* and you can mix and match these in any order.
*/
bool
-describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem)
+describeFunctions(const char *functypes, const char *func_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem)
{
bool showAggregate = strchr(functypes, 'a') != NULL;
bool showNormal = strchr(functypes, 'n') != NULL;
@@ -524,6 +526,14 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
"\nFROM pg_catalog.pg_proc p"
"\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace\n");
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ appendPQExpBuffer(&buf,
+ " LEFT JOIN pg_catalog.pg_type t%d ON t%d.oid = p.proargtypes[%d]\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt%d ON nt%d.oid = t%d.typnamespace\n",
+ i, i, i, i, i, i);
+ }
+
if (verbose)
appendPQExpBufferStr(&buf,
" LEFT JOIN pg_catalog.pg_language l ON l.oid = p.prolang\n");
@@ -629,11 +639,42 @@ describeFunctions(const char *functypes, const char *pattern, bool verbose, bool
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, pattern, have_where, false,
+ processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
"n.nspname", "p.proname", NULL,
"pg_catalog.pg_function_is_visible(p.oid)");
- if (!showSystem && !pattern)
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ if (strcmp(arg_patterns[i], "-") != 0)
+ {
+ /*
+ * Match type-name patterns against either internal or external
+ * name, like \dT. Unlike \dT, there seems no reason to
+ * discriminate against arrays or composite types.
+ */
+ char nspname[64];
+ char typname[64];
+ char ft[64];
+ char tiv[64];
+
+ snprintf(nspname, sizeof(nspname), "nt%d.nspname", i);
+ snprintf(typname, sizeof(typname), "t%d.typname", i);
+ snprintf(ft, sizeof(ft),
+ "pg_catalog.format_type(t%d.oid, NULL)", i);
+ snprintf(tiv, sizeof(tiv),
+ "pg_catalog.pg_type_is_visible(t%d.oid)", i);
+ processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ true, false,
+ nspname, typname, ft, tiv);
+ }
+ else
+ {
+ /* "-" pattern specifies no such parameter */
+ appendPQExpBuffer(&buf, " AND t%d.typname IS NULL\n", i);
+ }
+ }
+
+ if (!showSystem && !func_pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
@@ -787,7 +828,9 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
* Describe operators
*/
bool
-describeOperators(const char *pattern, bool verbose, bool showSystem)
+describeOperators(const char *oper_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem)
{
PQExpBufferData buf;
PGresult *res;
@@ -836,14 +879,65 @@ describeOperators(const char *pattern, bool verbose, bool showSystem)
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace\n",
gettext_noop("Description"));
- if (!showSystem && !pattern)
+ if (num_arg_patterns >= 2)
+ {
+ num_arg_patterns = 2; /* ignore any additional arguments */
+ appendPQExpBufferStr(&buf,
+ " LEFT JOIN pg_catalog.pg_type t0 ON t0.oid = o.oprleft\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n"
+ " LEFT JOIN pg_catalog.pg_type t1 ON t1.oid = o.oprright\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt1 ON nt1.oid = t1.typnamespace\n");
+ }
+ else if (num_arg_patterns == 1)
+ {
+ appendPQExpBufferStr(&buf,
+ " LEFT JOIN pg_catalog.pg_type t0 ON t0.oid = o.oprright\n"
+ " LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n");
+ }
+
+ if (!showSystem && !oper_pattern)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, true,
+ processSQLNamePattern(pset.db, &buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
"n.nspname", "o.oprname", NULL,
"pg_catalog.pg_operator_is_visible(o.oid)");
+ if (num_arg_patterns == 1)
+ appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
+
+ for (int i = 0; i < num_arg_patterns; i++)
+ {
+ if (strcmp(arg_patterns[i], "-") != 0)
+ {
+ /*
+ * Match type-name patterns against either internal or external
+ * name, like \dT. Unlike \dT, there seems no reason to
+ * discriminate against arrays or composite types.
+ */
+ char nspname[64];
+ char typname[64];
+ char ft[64];
+ char tiv[64];
+
+ snprintf(nspname, sizeof(nspname), "nt%d.nspname", i);
+ snprintf(typname, sizeof(typname), "t%d.typname", i);
+ snprintf(ft, sizeof(ft),
+ "pg_catalog.format_type(t%d.oid, NULL)", i);
+ snprintf(tiv, sizeof(tiv),
+ "pg_catalog.pg_type_is_visible(t%d.oid)", i);
+ processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ true, false,
+ nspname, typname, ft, tiv);
+ }
+ else
+ {
+ /* "-" pattern specifies no such parameter */
+ appendPQExpBuffer(&buf, " AND t%d.typname IS NULL\n", i);
+ }
+ }
+
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3, 4;");
res = PSQLexec(buf.data);
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index 39856a0c7e..71b320f1fc 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -19,13 +19,17 @@ extern bool describeAccessMethods(const char *pattern, bool verbose);
extern bool describeTablespaces(const char *pattern, bool verbose);
/* \df, \dfa, \dfn, \dft, \dfw, etc. */
-extern bool describeFunctions(const char *functypes, const char *pattern, bool verbose, bool showSystem);
+extern bool describeFunctions(const char *functypes, const char *func_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem);
/* \dT */
extern bool describeTypes(const char *pattern, bool verbose, bool showSystem);
/* \do */
-extern bool describeOperators(const char *pattern, bool verbose, bool showSystem);
+extern bool describeOperators(const char *oper_pattern,
+ char **arg_patterns, int num_arg_patterns,
+ bool verbose, bool showSystem);
/* \du, \dg */
extern bool describeRoles(const char *pattern, bool verbose, bool showSystem);
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index ac9a89a889..36501d5e2b 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -166,7 +166,7 @@ slashUsage(unsigned short int pager)
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
- output = PageOutput(133, pager ? &(pset.popt.topt) : NULL);
+ output = PageOutput(135, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
@@ -240,7 +240,8 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\des[+] [PATTERN] list foreign servers\n"));
fprintf(output, _(" \\deu[+] [PATTERN] list user mappings\n"));
fprintf(output, _(" \\dew[+] [PATTERN] list foreign-data wrappers\n"));
- fprintf(output, _(" \\df[anptw][S+] [PATRN] list [only agg/normal/procedures/trigger/window] functions\n"));
+ fprintf(output, _(" \\df[anptw][S+] [FUNCPTRN [TYPEPTRN ...]]\n"));
+ fprintf(output, _(" list [only agg/normal/procedure/trigger/window] functions\n"));
fprintf(output, _(" \\dF[+] [PATTERN] list text search configurations\n"));
fprintf(output, _(" \\dFd[+] [PATTERN] list text search dictionaries\n"));
fprintf(output, _(" \\dFp[+] [PATTERN] list text search parsers\n"));
@@ -251,7 +252,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dL[S+] [PATTERN] list procedural languages\n"));
fprintf(output, _(" \\dm[S+] [PATTERN] list materialized views\n"));
fprintf(output, _(" \\dn[S+] [PATTERN] list schemas\n"));
- fprintf(output, _(" \\do[S] [PATTERN] list operators\n"));
+ fprintf(output, _(" \\do[S] [OPPTRN [TYPEPTRN [TYPEPTRN]]] list operators\n"));
fprintf(output, _(" \\dO[S+] [PATTERN] list collations\n"));
fprintf(output, _(" \\dp [PATTERN] list table, view, and sequence access privileges\n"));
fprintf(output, _(" \\dP[itn+] [PATTERN] list [only index/table] partitioned relations [n=nested]\n"));
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 9a1ea9ab98..5b206c7481 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -1062,10 +1062,16 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* regexp errors. Outside quotes, however, let them pass through
* as-is; this lets knowledgeable users build regexp expressions
* that are more powerful than shell-style patterns.
+ *
+ * As an exception to that, though, always quote "[]", as that's
+ * much more likely to be an attempt to write an array type name
+ * than it is to be the start of a regexp bracket expression.
*/
if ((inquotes || force_escape) &&
strchr("|*+?()[]{}.^$\\", ch))
appendPQExpBufferChar(curbuf, '\\');
+ else if (ch == '[' && cp[1] == ']')
+ appendPQExpBufferChar(curbuf, '\\');
i = PQmblen(cp, encoding);
while (i-- && *cp)
{
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 9a51940530..672937b2f8 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5078,6 +5078,107 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- check \df, \do with argument specifications
+\df *sqrt
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | dsqrt | double precision | double precision | func
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | double precision | double precision | func
+ pg_catalog | sqrt | numeric | numeric | func
+(4 rows)
+
+\df *sqrt num*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+--------------+------------------+---------------------+------
+ pg_catalog | numeric_sqrt | numeric | numeric | func
+ pg_catalog | sqrt | numeric | numeric | func
+(2 rows)
+
+\df int*pl
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+---------------------+------
+ pg_catalog | int24pl | integer | smallint, integer | func
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int2pl | smallint | smallint, smallint | func
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+ pg_catalog | int82pl | bigint | bigint, smallint | func
+ pg_catalog | int84pl | bigint | bigint, integer | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+ pg_catalog | interval_pl | interval | interval, interval | func
+(10 rows)
+
+\df int*pl int4
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int42pl | integer | integer, smallint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int4pl | integer | integer, integer | func
+(3 rows)
+
+\df int*pl * pg_catalog.int8
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | int28pl | bigint | smallint, bigint | func
+ pg_catalog | int48pl | bigint | integer, bigint | func
+ pg_catalog | int8pl | bigint | bigint, bigint | func
+(3 rows)
+
+\df acl* aclitem[]
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+-------------+------------------+----------------------------------------------------------------------------------------------------+------
+ pg_catalog | aclcontains | boolean | aclitem[], aclitem | func
+ pg_catalog | aclexplode | SETOF record | acl aclitem[], OUT grantor oid, OUT grantee oid, OUT privilege_type text, OUT is_grantable boolean | func
+ pg_catalog | aclinsert | aclitem[] | aclitem[], aclitem | func
+ pg_catalog | aclremove | aclitem[] | aclitem[], aclitem | func
+(4 rows)
+
+\df has_database_privilege oid text
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+------------------------+------------------+---------------------+------
+ pg_catalog | has_database_privilege | boolean | oid, text | func
+ pg_catalog | has_database_privilege | boolean | oid, text, text | func
+(2 rows)
+
+\df has_database_privilege oid text -
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+------------------------+------------------+---------------------+------
+ pg_catalog | has_database_privilege | boolean | oid, text | func
+(1 row)
+
+\dfa bit* small*
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+------------+---------+------------------+---------------------+------
+ pg_catalog | bit_and | smallint | smallint | agg
+ pg_catalog | bit_or | smallint | smallint | agg
+ pg_catalog | bit_xor | smallint | smallint | agg
+(3 rows)
+
+\do - pg_catalog.int4
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+------------+------+---------------+----------------+-------------+-------------
+ pg_catalog | - | | integer | integer | negate
+(1 row)
+
+\do && anyarray *
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+------------+------+---------------+----------------+-------------+-------------
+ pg_catalog | && | anyarray | anyarray | boolean | overlaps
+(1 row)
+
--
-- combined queries
--
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index bf06bb87b5..f90a0270fc 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1229,6 +1229,19 @@ drop role regress_partitioning_role;
\dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops
+-- check \df, \do with argument specifications
+\df *sqrt
+\df *sqrt num*
+\df int*pl
+\df int*pl int4
+\df int*pl * pg_catalog.int8
+\df acl* aclitem[]
+\df has_database_privilege oid text
+\df has_database_privilege oid text -
+\dfa bit* small*
+\do - pg_catalog.int4
+\do && anyarray *
+
--
-- combined queries
--
v9-0002-simplify-use-of-dT.patchtext/x-diff; charset=us-ascii; name=v9-0002-simplify-use-of-dT.patchDownload
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index cd829e0759..e65fa07049 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -28,6 +28,7 @@
#include "settings.h"
#include "variables.h"
+static const char *map_typename_pattern(const char *pattern);
static bool describeOneTableDetails(const char *schemaname,
const char *relationname,
const char *oid,
@@ -663,7 +664,8 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ processSQLNamePattern(pset.db, &buf,
+ map_typename_pattern(arg_patterns[i]),
true, false,
nspname, typname, ft, tiv);
}
@@ -787,20 +789,24 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
"WHERE c.oid = t.typrelid))\n");
/*
- * do not include array types (before 8.3 we have to use the assumption
- * that their names start with underscore)
+ * do not include array types unless the pattern contains [] (before 8.3
+ * we have to use the assumption that their names start with underscore)
*/
- if (pset.sversion >= 80300)
- appendPQExpBufferStr(&buf, " AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)\n");
- else
- appendPQExpBufferStr(&buf, " AND t.typname !~ '^_'\n");
+ if (strstr(pattern, "[]") == NULL)
+ {
+ if (pset.sversion >= 80300)
+ appendPQExpBufferStr(&buf, " AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid)\n");
+ else
+ appendPQExpBufferStr(&buf, " AND t.typname !~ '^_'\n");
+ }
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
+ processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
+ true, false,
"n.nspname", "t.typname",
"pg_catalog.format_type(t.oid, NULL)",
"pg_catalog.pg_type_is_visible(t.oid)");
@@ -822,6 +828,60 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
return true;
}
+/*
+ * Map some variant type names accepted by the backend grammar into
+ * canonical type names.
+ *
+ * Helper for \dT and other functions that take typename patterns.
+ * This doesn't completely mask the fact that these names are special;
+ * for example, a pattern of "dec*" won't magically match "numeric".
+ * But it goes a long way to reduce the surprise factor.
+ */
+static const char *
+map_typename_pattern(const char *pattern)
+{
+ static const char *const typename_map[] = {
+ /*
+ * These names are accepted by gram.y, although they are neither the
+ * "real" name seen in pg_type nor the canonical name printed by
+ * format_type().
+ */
+ "decimal", "numeric",
+ "float", "double precision",
+ "int", "integer",
+
+ /*
+ * We also have to map the array names for cases where the canonical
+ * name is different from what pg_type says.
+ */
+ "bool[]", "boolean[]",
+ "decimal[]", "numeric[]",
+ "float[]", "double precision[]",
+ "float4[]", "real[]",
+ "float8[]", "double precision[]",
+ "int[]", "integer[]",
+ "int2[]", "smallint[]",
+ "int4[]", "integer[]",
+ "int8[]", "bigint[]",
+ "time[]", "time without time zone[]",
+ "timetz[]", "time with time zone[]",
+ "timestamp[]", "timestamp without time zone[]",
+ "timestamptz[]", "timestamp with time zone[]",
+ "varbit[]", "bit varying[]",
+ "varchar[]", "character varying[]",
+ NULL
+ };
+
+ if (pattern == NULL)
+ return NULL;
+ for (int i = 0; typename_map[i] != NULL; i += 2)
+ {
+ if (strcmp(pattern, typename_map[i]) == 0)
+ return typename_map[i + 1];
+ }
+ return pattern;
+}
+
/*
* \do
@@ -927,7 +987,8 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf, arg_patterns[i],
+ processSQLNamePattern(pset.db, &buf,
+ map_typename_pattern(arg_patterns[i]),
true, false,
nspname, typname, ft, tiv);
}