Skip to content

Commit 2355e51

Browse files
committed
psql: Add leakproof indicator to \df+, \do+, \dAo+, and \dC+ output.
This allows users to determine whether particular functions are leakproof, and whether the underlying functions used by operators and casts are leakproof. This is useful to determine whether indexes can be used in queries on security barrier views or tables with row-level security policies. Yugo Nagata, reviewed by Erik Wienhold and Dean Rasheed. Discussion: https://postgr.es/m/20240701220817.483f9b645b95611f8b1f65da%40sranhm.sraoss.co.jp
1 parent af8cd16 commit 2355e51

File tree

6 files changed

+107
-56
lines changed

6 files changed

+107
-56
lines changed

doc/src/sgml/planstats.sgml

+3
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,9 @@ EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a <= 49 AND
729729
accurately, the function that the operator is based on). If not, then the
730730
selectivity estimator will behave as if no statistics are available, and
731731
the planner will proceed with default or fall-back assumptions.
732+
The <xref linkend="app-psql"/> program's
733+
<command><link linkend="app-psql-meta-command-do-lc">\do+</link></command>
734+
meta-command is useful to determine which operators are marked as leakproof.
732735
</para>
733736

734737
<para>

doc/src/sgml/ref/psql-ref.sgml

+10-8
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,8 @@ SELECT $1 \parse stmt1
14201420
is specified, only members of operator families whose names match that
14211421
pattern are listed.
14221422
If <literal>+</literal> is appended to the command name, each operator
1423-
is listed with its sort operator family (if it is an ordering operator).
1423+
is listed with its sort operator family (if it is an ordering operator),
1424+
and whether its underlying function is leakproof.
14241425
</para>
14251426
</listitem>
14261427
</varlistentry>
@@ -1509,8 +1510,9 @@ SELECT $1 \parse stmt1
15091510
If <replaceable class="parameter">pattern</replaceable>
15101511
is specified, only casts whose source or target types match the
15111512
pattern are listed.
1512-
If <literal>+</literal> is appended to the command name, each object
1513-
is listed with its associated description.
1513+
If <literal>+</literal> is appended to the command name, additional
1514+
information about each cast is shown, including whether its underlying
1515+
function is leakproof, and the cast's description.
15141516
</para>
15151517
</listitem>
15161518
</varlistentry>
@@ -1711,9 +1713,9 @@ SELECT $1 \parse stmt1
17111713
modifier to include system objects.
17121714
If the form <literal>\df+</literal> is used, additional information
17131715
about each function is shown, including volatility,
1714-
parallel safety, owner, security classification, access privileges,
1715-
language, internal name (for C and internal functions only),
1716-
and description.
1716+
parallel safety, owner, security classification, whether it is
1717+
leakproof, access privileges, language, internal name (for C and
1718+
internal functions only), and description.
17171719
Source code for a specific function can be seen
17181720
using <literal>\sf</literal>.
17191721
</para>
@@ -1862,8 +1864,8 @@ SELECT $1 \parse stmt1
18621864
pattern or the <literal>S</literal> modifier to include system
18631865
objects.
18641866
If <literal>+</literal> is appended to the command name,
1865-
additional information about each operator is shown, currently just
1866-
the name of the underlying function.
1867+
additional information about each operator is shown, including
1868+
the name of the underlying function, and whether it is leakproof.
18671869
</para>
18681870
</listitem>
18691871
</varlistentry>

doc/src/sgml/rules.sgml

+11
Original file line numberDiff line numberDiff line change
@@ -2167,6 +2167,17 @@ CREATE VIEW phone_number WITH (security_barrier) AS
21672167
view's row filters.
21682168
</para>
21692169

2170+
<para>
2171+
For example, an index scan cannot be selected for queries on security
2172+
barrier views (or tables with row-level security policies) if an
2173+
operator used in the <literal>WHERE</literal> clause is associated with the
2174+
operator family of the index, but its underlying function is not marked
2175+
<literal>LEAKPROOF</literal>. The <xref linkend="app-psql"/> program's
2176+
<command><link linkend="app-psql-meta-command-dao">\dAo+</link></command>
2177+
meta-command is useful to list operator families and determine which of
2178+
their operators are marked as leakproof.
2179+
</para>
2180+
21702181
<para>
21712182
It is important to understand that even a view created with the
21722183
<literal>security_barrier</literal> option is intended to be secure only

src/bin/psql/describe.c

+42-12
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,10 @@ describeFunctions(const char *functypes, const char *func_pattern,
304304
PQExpBufferData buf;
305305
PGresult *res;
306306
printQueryOpt myopt = pset.popt;
307-
static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, false, false, false, false};
307+
static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, true, false, false, false, false};
308308

309309
/* No "Parallel" column before 9.6 */
310-
static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, false, false, false, false};
310+
static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, true, false, false, false, false};
311311

312312
if (strlen(functypes) != strspn(functypes, "anptwS+"))
313313
{
@@ -409,11 +409,15 @@ describeFunctions(const char *functypes, const char *func_pattern,
409409
gettext_noop("Parallel"));
410410
appendPQExpBuffer(&buf,
411411
",\n pg_catalog.pg_get_userbyid(p.proowner) as \"%s\""
412-
",\n CASE WHEN prosecdef THEN '%s' ELSE '%s' END AS \"%s\"",
412+
",\n CASE WHEN prosecdef THEN '%s' ELSE '%s' END AS \"%s\""
413+
",\n CASE WHEN p.proleakproof THEN '%s' ELSE '%s' END as \"%s\"",
413414
gettext_noop("Owner"),
414415
gettext_noop("definer"),
415416
gettext_noop("invoker"),
416-
gettext_noop("Security"));
417+
gettext_noop("Security"),
418+
gettext_noop("yes"),
419+
gettext_noop("no"),
420+
gettext_noop("Leakproof?"));
417421
appendPQExpBufferStr(&buf, ",\n ");
418422
printACLColumn(&buf, "p.proacl");
419423
appendPQExpBuffer(&buf,
@@ -792,6 +796,7 @@ describeOperators(const char *oper_pattern,
792796
PQExpBufferData buf;
793797
PGresult *res;
794798
printQueryOpt myopt = pset.popt;
799+
static const bool translate_columns[] = {false, false, false, false, false, false, true, false};
795800

796801
initPQExpBuffer(&buf);
797802

@@ -825,8 +830,12 @@ describeOperators(const char *oper_pattern,
825830

826831
if (verbose)
827832
appendPQExpBuffer(&buf,
828-
" o.oprcode AS \"%s\",\n",
829-
gettext_noop("Function"));
833+
" o.oprcode AS \"%s\",\n"
834+
" CASE WHEN p.proleakproof THEN '%s' ELSE '%s' END AS \"%s\",\n",
835+
gettext_noop("Function"),
836+
gettext_noop("yes"),
837+
gettext_noop("no"),
838+
gettext_noop("Leakproof?"));
830839

831840
appendPQExpBuffer(&buf,
832841
" coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n"
@@ -851,6 +860,10 @@ describeOperators(const char *oper_pattern,
851860
" LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n");
852861
}
853862

863+
if (verbose)
864+
appendPQExpBufferStr(&buf,
865+
" LEFT JOIN pg_catalog.pg_proc p ON p.oid = o.oprcode\n");
866+
854867
if (!showSystem && !oper_pattern)
855868
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
856869
" AND n.nspname <> 'information_schema'\n");
@@ -908,6 +921,8 @@ describeOperators(const char *oper_pattern,
908921

909922
myopt.title = _("List of operators");
910923
myopt.translate_header = true;
924+
myopt.translate_columns = translate_columns;
925+
myopt.n_translate_columns = lengthof(translate_columns);
911926

912927
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
913928

@@ -4886,7 +4901,7 @@ listCasts(const char *pattern, bool verbose)
48864901
PQExpBufferData buf;
48874902
PGresult *res;
48884903
printQueryOpt myopt = pset.popt;
4889-
static const bool translate_columns[] = {false, false, false, true, false};
4904+
static const bool translate_columns[] = {false, false, false, true, true, false};
48904905

48914906
initPQExpBuffer(&buf);
48924907

@@ -4924,7 +4939,13 @@ listCasts(const char *pattern, bool verbose)
49244939

49254940
if (verbose)
49264941
appendPQExpBuffer(&buf,
4927-
",\n d.description AS \"%s\"",
4942+
",\n CASE WHEN p.proleakproof THEN '%s'\n"
4943+
" ELSE '%s'\n"
4944+
" END AS \"%s\",\n"
4945+
" d.description AS \"%s\"",
4946+
gettext_noop("yes"),
4947+
gettext_noop("no"),
4948+
gettext_noop("Leakproof?"),
49284949
gettext_noop("Description"));
49294950

49304951
/*
@@ -6987,7 +7008,7 @@ listOpFamilyOperators(const char *access_method_pattern,
69877008
printQueryOpt myopt = pset.popt;
69887009
bool have_where = false;
69897010

6990-
static const bool translate_columns[] = {false, false, false, false, false, false};
7011+
static const bool translate_columns[] = {false, false, false, false, false, false, true};
69917012

69927013
initPQExpBuffer(&buf);
69937014

@@ -7015,16 +7036,25 @@ listOpFamilyOperators(const char *access_method_pattern,
70157036

70167037
if (verbose)
70177038
appendPQExpBuffer(&buf,
7018-
", ofs.opfname AS \"%s\"\n",
7019-
gettext_noop("Sort opfamily"));
7039+
", ofs.opfname AS \"%s\",\n"
7040+
" CASE\n"
7041+
" WHEN p.proleakproof THEN '%s'\n"
7042+
" ELSE '%s'\n"
7043+
" END AS \"%s\"\n",
7044+
gettext_noop("Sort opfamily"),
7045+
gettext_noop("yes"),
7046+
gettext_noop("no"),
7047+
gettext_noop("Leakproof?"));
70207048
appendPQExpBufferStr(&buf,
70217049
"FROM pg_catalog.pg_amop o\n"
70227050
" LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = o.amopfamily\n"
70237051
" LEFT JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod AND am.oid = o.amopmethod\n"
70247052
" LEFT JOIN pg_catalog.pg_namespace nsf ON of.opfnamespace = nsf.oid\n");
70257053
if (verbose)
70267054
appendPQExpBufferStr(&buf,
7027-
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
7055+
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n"
7056+
" LEFT JOIN pg_catalog.pg_operator op ON op.oid = o.amopopr\n"
7057+
" LEFT JOIN pg_catalog.pg_proc p ON p.oid = op.oprcode\n");
70287058

70297059
if (access_method_pattern)
70307060
{

src/test/regress/expected/psql.out

+40-35
Original file line numberDiff line numberDiff line change
@@ -5184,31 +5184,36 @@ List of access methods
51845184
btree | integer_ops | smallint, integer, bigint
51855185
(1 row)
51865186

5187-
\dAo+ btree float_ops
5188-
List of operators of operator families
5189-
AM | Operator family | Operator | Strategy | Purpose | Sort opfamily
5190-
-------+-----------------+---------------------------------------+----------+---------+---------------
5191-
btree | float_ops | <(double precision,double precision) | 1 | search |
5192-
btree | float_ops | <=(double precision,double precision) | 2 | search |
5193-
btree | float_ops | =(double precision,double precision) | 3 | search |
5194-
btree | float_ops | >=(double precision,double precision) | 4 | search |
5195-
btree | float_ops | >(double precision,double precision) | 5 | search |
5196-
btree | float_ops | <(real,real) | 1 | search |
5197-
btree | float_ops | <=(real,real) | 2 | search |
5198-
btree | float_ops | =(real,real) | 3 | search |
5199-
btree | float_ops | >=(real,real) | 4 | search |
5200-
btree | float_ops | >(real,real) | 5 | search |
5201-
btree | float_ops | <(double precision,real) | 1 | search |
5202-
btree | float_ops | <=(double precision,real) | 2 | search |
5203-
btree | float_ops | =(double precision,real) | 3 | search |
5204-
btree | float_ops | >=(double precision,real) | 4 | search |
5205-
btree | float_ops | >(double precision,real) | 5 | search |
5206-
btree | float_ops | <(real,double precision) | 1 | search |
5207-
btree | float_ops | <=(real,double precision) | 2 | search |
5208-
btree | float_ops | =(real,double precision) | 3 | search |
5209-
btree | float_ops | >=(real,double precision) | 4 | search |
5210-
btree | float_ops | >(real,double precision) | 5 | search |
5211-
(20 rows)
5187+
\dAo+ btree array_ops|float_ops
5188+
List of operators of operator families
5189+
AM | Operator family | Operator | Strategy | Purpose | Sort opfamily | Leakproof?
5190+
-------+-----------------+---------------------------------------+----------+---------+---------------+------------
5191+
btree | array_ops | <(anyarray,anyarray) | 1 | search | | no
5192+
btree | array_ops | <=(anyarray,anyarray) | 2 | search | | no
5193+
btree | array_ops | =(anyarray,anyarray) | 3 | search | | no
5194+
btree | array_ops | >=(anyarray,anyarray) | 4 | search | | no
5195+
btree | array_ops | >(anyarray,anyarray) | 5 | search | | no
5196+
btree | float_ops | <(double precision,double precision) | 1 | search | | yes
5197+
btree | float_ops | <=(double precision,double precision) | 2 | search | | yes
5198+
btree | float_ops | =(double precision,double precision) | 3 | search | | yes
5199+
btree | float_ops | >=(double precision,double precision) | 4 | search | | yes
5200+
btree | float_ops | >(double precision,double precision) | 5 | search | | yes
5201+
btree | float_ops | <(real,real) | 1 | search | | yes
5202+
btree | float_ops | <=(real,real) | 2 | search | | yes
5203+
btree | float_ops | =(real,real) | 3 | search | | yes
5204+
btree | float_ops | >=(real,real) | 4 | search | | yes
5205+
btree | float_ops | >(real,real) | 5 | search | | yes
5206+
btree | float_ops | <(double precision,real) | 1 | search | | yes
5207+
btree | float_ops | <=(double precision,real) | 2 | search | | yes
5208+
btree | float_ops | =(double precision,real) | 3 | search | | yes
5209+
btree | float_ops | >=(double precision,real) | 4 | search | | yes
5210+
btree | float_ops | >(double precision,real) | 5 | search | | yes
5211+
btree | float_ops | <(real,double precision) | 1 | search | | yes
5212+
btree | float_ops | <=(real,double precision) | 2 | search | | yes
5213+
btree | float_ops | =(real,double precision) | 3 | search | | yes
5214+
btree | float_ops | >=(real,double precision) | 4 | search | | yes
5215+
btree | float_ops | >(real,double precision) | 5 | search | | yes
5216+
(25 rows)
52125217

52135218
\dAo * pg_catalog.jsonb_path_ops
52145219
List of operators of operator families
@@ -5388,12 +5393,12 @@ create function psql_df_plpgsql ()
53885393
as $$ begin return; end; $$;
53895394
comment on function psql_df_plpgsql () is 'some comment';
53905395
\df+ psql_df_*
5391-
List of functions
5392-
Schema | Name | Result data type | Argument data types | Type | Volatility | Parallel | Owner | Security | Access privileges | Language | Internal name | Description
5393-
--------+------------------+------------------+---------------------+------+------------+----------+-------------------+----------+-------------------+----------+---------------+--------------
5394-
public | psql_df_internal | double precision | double precision | func | immutable | safe | regress_psql_user | invoker | | internal | dsin |
5395-
public | psql_df_plpgsql | void | | func | volatile | unsafe | regress_psql_user | invoker | | plpgsql | | some comment
5396-
public | psql_df_sql | integer | x integer | func | volatile | unsafe | regress_psql_user | definer | | sql | |
5396+
List of functions
5397+
Schema | Name | Result data type | Argument data types | Type | Volatility | Parallel | Owner | Security | Leakproof? | Access privileges | Language | Internal name | Description
5398+
--------+------------------+------------------+---------------------+------+------------+----------+-------------------+----------+------------+-------------------+----------+---------------+--------------
5399+
public | psql_df_internal | double precision | double precision | func | immutable | safe | regress_psql_user | invoker | no | | internal | dsin |
5400+
public | psql_df_plpgsql | void | | func | volatile | unsafe | regress_psql_user | invoker | no | | plpgsql | | some comment
5401+
public | psql_df_sql | integer | x integer | func | volatile | unsafe | regress_psql_user | definer | no | | sql | |
53975402
(3 rows)
53985403

53995404
rollback;
@@ -6791,10 +6796,10 @@ REVOKE ALL ON DOMAIN regress_zeropriv_domain FROM CURRENT_USER, PUBLIC;
67916796
CREATE PROCEDURE regress_zeropriv_proc() LANGUAGE sql AS '';
67926797
REVOKE ALL ON PROCEDURE regress_zeropriv_proc() FROM CURRENT_USER, PUBLIC;
67936798
\df+ regress_zeropriv_proc
6794-
List of functions
6795-
Schema | Name | Result data type | Argument data types | Type | Volatility | Parallel | Owner | Security | Access privileges | Language | Internal name | Description
6796-
--------+-----------------------+------------------+---------------------+------+------------+----------+------------------------+----------+-------------------+----------+---------------+-------------
6797-
public | regress_zeropriv_proc | | | proc | volatile | unsafe | regress_zeropriv_owner | invoker | (none) | sql | |
6799+
List of functions
6800+
Schema | Name | Result data type | Argument data types | Type | Volatility | Parallel | Owner | Security | Leakproof? | Access privileges | Language | Internal name | Description
6801+
--------+-----------------------+------------------+---------------------+------+------------+----------+------------------------+----------+------------+-------------------+----------+---------------+-------------
6802+
public | regress_zeropriv_proc | | | proc | volatile | unsafe | regress_zeropriv_owner | invoker | no | (none) | sql | |
67986803
(1 row)
67996804

68006805
CREATE TABLE regress_zeropriv_tbl (a int);

src/test/regress/sql/psql.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -1306,7 +1306,7 @@ drop role regress_partitioning_role;
13061306
\dAc brin pg*.oid*
13071307
\dAf spgist
13081308
\dAf btree int4
1309-
\dAo+ btree float_ops
1309+
\dAo+ btree array_ops|float_ops
13101310
\dAo * pg_catalog.jsonb_path_ops
13111311
\dAp+ btree float_ops
13121312
\dAp * pg_catalog.uuid_ops

0 commit comments

Comments
 (0)