Subject: Re: psql: Add leakproof field to \dAo+ meta-command results

From: Yugo NAGATA <[email-masked]>
Date: Tue, 30 Jul 2024 15:30:57 +0900

Hi,

On Tue, 30 Jul 2024 01:36:55 +0200
Erik Wienhold <[email-masked]> wrote:

> On 2024-07-01 15:08 +0200, Yugo NAGATA wrote:
> > I would like to propose to add a new field to psql's \dAo+ meta-command
> > to show whether the underlying function of an operator is leak-proof.
> 
> +1 for making that info easily accessible.
> 
> > This idea is inspired from [1] that claims some indexes uses non-LEAKPROOF
> > functions under the associated operators, as a result, it can not be selected
> > for queries with security_barrier views or row-level security policies.
> > The original proposal was to add a query over system catalogs for looking up
> > non-leakproof operators to the documentation, but I thought it is useful
> > to improve \dAo results rather than putting such query to the doc.
> > 
> > The attached patch adds the field to \dAo+ and also a description that
> > explains the relation between indexes and security quals with referencing
> > \dAo+ meta-command.
> > 
> > [1] https://www.postgresql.org/message-id/raw/5af3bf0c-5e0c-4128-81dc-084c5258b1af%40code406.com
> 
> \dAo+ output looks good.

Thank you for looking into this.
I attached a patch updated with your suggestions.

> 
> But this patch fails regression tests in src/test/regress/sql/psql.sql
> (\dAo+ btree float_ops) because of the new leak-proof column.  I think
> this could even be changed to "\dAo+ btree array_ops|float_ops" to also
> cover operators that are not leak-proof.

Thank you for pointing out this. I fixed it with you suggestion to cover
non leak-proof operators, too.

> +<para>
> +    For example, an index scan can not be selected for queries with
> 
> I check the docs and "cannot" is more commonly used than "can not".

Fixed.

> 
> +    <literal>security_barrier</literal> views or row-level security policies if an
> +    operator used in the <literal>WHERE</literal> clause is associated with the
> +    operator family of the index, but its underlying function is not marked
> +    <literal>LEAKPROOF</literal>. The <xref linkend="app-psql"/> program's
> +    <command>\dAo+</command> meta-command is useful for listing the operators
> +    with associated operator families and whether it is leak-proof.
> +</para>
> 
> I think the last sentence can be improved.  How about: "Use psql's \dAo+
> command to list operator families and tell which of their operators are
> marked as leak-proof."?  Should something similar be added to [1] which
> also talks about leak-proof operators?

I agree, so I fixed the sentence as your suggestion and also add the
same description to the planner-stats-security doc.

> The rest is just formatting nitpicks:
> 
> +                         ", ofs.opfname AS \"%s\"\n,"
> 
> The trailing comma should come before the newline.
> 
> +                         "  CASE\n"
> +                         "   WHEN p.proleakproof THEN '%s'\n"
> +                         "   ELSE '%s'\n"
> +                         " END AS \"%s\"\n",
> 
> WHEN/ELSE/END should be intended with one additional space to be
> consistent with the other CASE expressions in this query.

Fixed both.

Regards,
Yugo Nagata

> 
> [1] https://www.postgresql.org/docs/devel/planner-stats-security.html
> 
> -- 
> Erik

-- 
Yugo NAGATA <[email-masked]>
>From ca41705da15ca588d55f3c2cc2106284911e53a1 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email-masked]>
Date: Mon, 1 Jul 2024 16:16:39 +0900
Subject: [PATCH v2] psql: Add leakproof field to \dAo+ meta-command results

This adds a field that shows whether the underlying function of an
operator associated with operator families is leak-proof.
It is useful for checking an index can be used with security_barrier
views or row-level security policies when the query's WHERE
clause contains an operator which is associated with the index.
---
 doc/src/sgml/planstats.sgml        |  2 ++
 doc/src/sgml/ref/psql-ref.sgml     |  3 +-
 doc/src/sgml/rules.sgml            | 10 ++++++
 src/bin/psql/describe.c            | 17 ++++++---
 src/test/regress/expected/psql.out | 55 ++++++++++++++++--------------
 src/test/regress/sql/psql.sql      |  2 +-
 6 files changed, 58 insertions(+), 31 deletions(-)

diff --git a/doc/src/sgml/planstats.sgml b/doc/src/sgml/planstats.sgml
index c7ec749d0a..45b0d2b765 100644
--- a/doc/src/sgml/planstats.sgml
+++ b/doc/src/sgml/planstats.sgml
@@ -729,6 +729,8 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a &lt;= 49 AND b &gt; 49;
    accurately, the function that the operator is based on).  If not, then the
    selectivity estimator will behave as if no statistics are available, and
    the planner will proceed with default or fall-back assumptions.
+   Use <xref linkend="app-psql"/>'s <command>\dAo+</command> command to list
+   operator families and tell which of their operators are marked as leak-proof.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 07419a3b92..4d55472929 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1363,7 +1363,8 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
         is specified, only members of operator families whose names match that
         pattern are listed.
         If <literal>+</literal> is appended to the command name, each operator
-        is listed with its sort operator family (if it is an ordering operator).
+        is listed with its sort operator family (if it is an ordering operator),
+        and whether it is leak-proof.
         </para>
         </listitem>
       </varlistentry>
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 7a928bd7b9..3f0c26dad3 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -2167,6 +2167,16 @@ CREATE VIEW phone_number WITH (security_barrier) AS
     view's row filters.
 </para>
 
+<para>
+    For example, an index scan cannot be selected for queries with
+    <literal>security_barrier</literal> views or row-level security policies if an
+    operator used in the <literal>WHERE</literal> clause is associated with the
+    operator family of the index, but its underlying function is not marked
+    <literal>LEAKPROOF</literal>. Use <xref linkend="app-psql"/>'s
+    <command>\dAo+</command> command to list operator families and tell which of
+    their operators are marked as leak-proof.
+</para>
+
 <para>
     It is important to understand that even a view created with the
     <literal>security_barrier</literal> option is intended to be secure only
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 7c9a1f234c..d1b2910073 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -6882,7 +6882,7 @@ listOpFamilyOperators(const char *access_method_pattern,
 	printQueryOpt myopt = pset.popt;
 	bool		have_where = false;
 
-	static const bool translate_columns[] = {false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, false};
 
 	initPQExpBuffer(&buf);
 
@@ -6910,8 +6910,15 @@ listOpFamilyOperators(const char *access_method_pattern,
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
-						  ", ofs.opfname AS \"%s\"\n",
-						  gettext_noop("Sort opfamily"));
+						  ", ofs.opfname AS \"%s\",\n"
+						  "  CASE\n"
+						  "    WHEN p.proleakproof THEN '%s'\n"
+						  "    ELSE '%s'\n"
+						  "  END AS \"%s\"\n",
+						  gettext_noop("Sort opfamily"),
+						  gettext_noop("yes"),
+						  gettext_noop("no"),
+						  gettext_noop("Leak-proof"));
 	appendPQExpBufferStr(&buf,
 						 "FROM pg_catalog.pg_amop o\n"
 						 "  LEFT JOIN pg_catalog.pg_opfamily of ON of.oid = o.amopfamily\n"
@@ -6919,7 +6926,9 @@ listOpFamilyOperators(const char *access_method_pattern,
 						 "  LEFT JOIN pg_catalog.pg_namespace nsf ON of.opfnamespace = nsf.oid\n");
 	if (verbose)
 		appendPQExpBufferStr(&buf,
-							 "  LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
+							 "  LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n"
+							 "  LEFT JOIN pg_catalog.pg_operator op ON op.oid = o.amopopr\n"
+							 "  LEFT JOIN pg_catalog.pg_proc p ON p.oid = op.oprcode\n");
 
 	if (access_method_pattern)
 	{
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3bbe4c5f97..7632f252c6 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5082,31 +5082,36 @@ List of access methods
  btree | integer_ops     | smallint, integer, bigint
 (1 row)
 
-\dAo+ btree float_ops
-                                List of operators of operator families
-  AM   | Operator family |               Operator                | Strategy | Purpose | Sort opfamily 
--------+-----------------+---------------------------------------+----------+---------+---------------
- btree | float_ops       | <(double precision,double precision)  |        1 | search  | 
- btree | float_ops       | <=(double precision,double precision) |        2 | search  | 
- btree | float_ops       | =(double precision,double precision)  |        3 | search  | 
- btree | float_ops       | >=(double precision,double precision) |        4 | search  | 
- btree | float_ops       | >(double precision,double precision)  |        5 | search  | 
- btree | float_ops       | <(real,real)                          |        1 | search  | 
- btree | float_ops       | <=(real,real)                         |        2 | search  | 
- btree | float_ops       | =(real,real)                          |        3 | search  | 
- btree | float_ops       | >=(real,real)                         |        4 | search  | 
- btree | float_ops       | >(real,real)                          |        5 | search  | 
- btree | float_ops       | <(double precision,real)              |        1 | search  | 
- btree | float_ops       | <=(double precision,real)             |        2 | search  | 
- btree | float_ops       | =(double precision,real)              |        3 | search  | 
- btree | float_ops       | >=(double precision,real)             |        4 | search  | 
- btree | float_ops       | >(double precision,real)              |        5 | search  | 
- btree | float_ops       | <(real,double precision)              |        1 | search  | 
- btree | float_ops       | <=(real,double precision)             |        2 | search  | 
- btree | float_ops       | =(real,double precision)              |        3 | search  | 
- btree | float_ops       | >=(real,double precision)             |        4 | search  | 
- btree | float_ops       | >(real,double precision)              |        5 | search  | 
-(20 rows)
+\dAo+ btree array_ops|float_ops
+                                      List of operators of operator families
+  AM   | Operator family |               Operator                | Strategy | Purpose | Sort opfamily | Leak-proof 
+-------+-----------------+---------------------------------------+----------+---------+---------------+------------
+ btree | array_ops       | <(anyarray,anyarray)                  |        1 | search  |               | no
+ btree | array_ops       | <=(anyarray,anyarray)                 |        2 | search  |               | no
+ btree | array_ops       | =(anyarray,anyarray)                  |        3 | search  |               | no
+ btree | array_ops       | >=(anyarray,anyarray)                 |        4 | search  |               | no
+ btree | array_ops       | >(anyarray,anyarray)                  |        5 | search  |               | no
+ btree | float_ops       | <(double precision,double precision)  |        1 | search  |               | yes
+ btree | float_ops       | <=(double precision,double precision) |        2 | search  |               | yes
+ btree | float_ops       | =(double precision,double precision)  |        3 | search  |               | yes
+ btree | float_ops       | >=(double precision,double precision) |        4 | search  |               | yes
+ btree | float_ops       | >(double precision,double precision)  |        5 | search  |               | yes
+ btree | float_ops       | <(real,real)                          |        1 | search  |               | yes
+ btree | float_ops       | <=(real,real)                         |        2 | search  |               | yes
+ btree | float_ops       | =(real,real)                          |        3 | search  |               | yes
+ btree | float_ops       | >=(real,real)                         |        4 | search  |               | yes
+ btree | float_ops       | >(real,real)                          |        5 | search  |               | yes
+ btree | float_ops       | <(double precision,real)              |        1 | search  |               | yes
+ btree | float_ops       | <=(double precision,real)             |        2 | search  |               | yes
+ btree | float_ops       | =(double precision,real)              |        3 | search  |               | yes
+ btree | float_ops       | >=(double precision,real)             |        4 | search  |               | yes
+ btree | float_ops       | >(double precision,real)              |        5 | search  |               | yes
+ btree | float_ops       | <(real,double precision)              |        1 | search  |               | yes
+ btree | float_ops       | <=(real,double precision)             |        2 | search  |               | yes
+ btree | float_ops       | =(real,double precision)              |        3 | search  |               | yes
+ btree | float_ops       | >=(real,double precision)             |        4 | search  |               | yes
+ btree | float_ops       | >(real,double precision)              |        5 | search  |               | yes
+(25 rows)
 
 \dAo * pg_catalog.jsonb_path_ops
              List of operators of operator families
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 3b3c6f6e29..d59eae1814 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1266,7 +1266,7 @@ drop role regress_partitioning_role;
 \dAc brin pg*.oid*
 \dAf spgist
 \dAf btree int4
-\dAo+ btree float_ops
+\dAo+ btree array_ops|float_ops
 \dAo * pg_catalog.jsonb_path_ops
 \dAp+ btree float_ops
 \dAp * pg_catalog.uuid_ops
-- 
2.34.1

---

From: Erik Wienhold <[email-masked]>
Date: Sun, 4 Aug 2024 23:23:03 +0200

> I attached a patch updated with your suggestions.

LGTM, thanks.

-- 
Erik

---

From: Dean Rasheed <[email-masked]>
Date: Mon, 4 Nov 2024 11:00:41 +0000

> to show whether the underlying function of an operator is leak-proof.

I agree that this is useful information to have, but why add it to
\dAo+ instead of \do+? Taking the example from the original thread,
when writing a query containing 'tsvector @@ tsquery', it's much more
obvious to use "\do+ @@" to check if it's leakproof, rather than
"\dAo+ gin".

Perhaps it would be useful to have this in \df+ output as well.

I notice that this patch spells "leakproof" with a hyphen. IMO
leakproof should not have a hyphen -- at least, that's how I naturally
spell it, and I think that's more common, and it matches the SQL
syntax.

We haven't been consistent about that in the docs and code comments so
far though, so I think we should make a decision, and then standardise
on whatever people decide.

Regards,
Dean

---

From: Yugo Nagata <[email-masked]>
Date: Fri, 15 Nov 2024 18:55:04 +0900

On Mon, 4 Nov 2024 11:00:41 +0000

> obvious to use "\do+ @@" to check if it's leakproof, rather than

I added it to \dAo+ since the initial motivation was that it enables to
check whether we can use an index scan for scanning a table which has RLS
policy when the condition contains a certain operator. However, as you
suggested, adding it to \do+ seems enough to know conditions using specified
operators can use indexes. 

I'll fixed the patch to add leakproof info to \do+ results, but is it worth
leaving this info in \dAo+ results, too? 

> Perhaps it would be useful to have this in \df+ output as well.

Agreed. I'll add the info to \df+, too.

> spell it, and I think that's more common, and it matches the SQL

OK, I'll fix it to use "leakproof" without a hyphen.
 
> on whatever people decide.

I am not a native English speaker, but if this is natural spelling in
English, I wonder we can replace all of them to leakproof without a hyphen. 

Regards,
Yugo Nagata

-- 
Yugo Nagata <[email-masked]>

---

From: Dean Rasheed <[email-masked]>
Date: Fri, 15 Nov 2024 17:26:08 +0000

> leaving this info in \dAo+ results, too?

I suppose that might still be useful in some contexts.

Looking through the complete list of psql meta-commands, "leakproof"
could plausibly be added to the output of each of the following:

\dAo+
\dC+
\df+
\do+

> English, I wonder we can replace all of them to leakproof without a hyphen.

Yes, I think we should do that (in a separate patch).

Regards,
Dean

---

From: Michael Paquier <[email-masked]>
Date: Tue, 19 Nov 2024 09:57:57 +0900

> Yes, I think we should do that (in a separate patch).

$ git grep "leakproof" | wc -l
544
$ git grep "leak-proof" | wc -l
8

So there's a clear winner here.
--
Michael

---

From: Yugo NAGATA <[email-masked]>
Date: Wed, 4 Dec 2024 20:20:57 +0900

On Fri, 15 Nov 2024 17:26:08 +0000

> could plausibly be added to the output of each of the following:

I've attached a updated patch (v3-0001) that include changes on all
of these meta-commands.

> Yes, I think we should do that (in a separate patch).

And, the patch v3-0002 is for that.

Regards,
Yugo Nagata

>From d4775d1c694a1c1cb72a430072d94394ac4880d9 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email-masked]>
Date: Wed, 4 Dec 2024 20:14:21 +0900
Subject: [PATCH v3 2/2] Doc: replace "leak-proof" in documents and comments
 with "leakproof"

---
 doc/src/sgml/catalogs.sgml              | 2 +-
 doc/src/sgml/planstats.sgml             | 2 +-
 doc/src/sgml/rules.sgml                 | 2 +-
 src/backend/statistics/extended_stats.c | 4 ++--
 src/backend/utils/adt/selfuncs.c        | 4 ++--
 src/include/catalog/pg_proc.h           | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bf3cee08a9..d1d21c30a7 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6041,7 +6041,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        The function has no side effects.  No information about the
        arguments is conveyed except via the return value.  Any function
        that might throw an error depending on the values of its arguments
-       is not leak-proof.
+       is not leakproof.
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/planstats.sgml b/doc/src/sgml/planstats.sgml
index c2a7142453..eb391e876a 100644
--- a/doc/src/sgml/planstats.sgml
+++ b/doc/src/sgml/planstats.sgml
@@ -739,7 +739,7 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a &lt;= 49 AND b &gt; 49;
    error, in which case this mechanism is invisible in practice.  But if the
    user is reading from a security-barrier view, then the planner might wish
    to check the statistics of an underlying table that is otherwise
-   inaccessible to the user.  In that case, the operator should be leak-proof
+   inaccessible to the user.  In that case, the operator should be leakproof
    or the statistics will not be used.  There is no direct feedback about
    that, except that the plan might be suboptimal.  If one suspects that this
    is the case, one could try running the query as a more privileged user,
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 74cd1d29fd..65a17f3065 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -2162,7 +2162,7 @@ CREATE VIEW phone_number WITH (security_barrier) AS
     <literal>LEAKPROOF</literal> to be pushed down, as they never receive data
     from the view.  In contrast, a function that might throw an error depending
     on the values received as arguments (such as one that throws an error in the
-    event of overflow or division by zero) is not leak-proof, and could provide
+    event of overflow or division by zero) is not leakproof, and could provide
     significant information about the unseen rows if applied before the security
     view's row filters.
 </para>
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 99fdf208db..e64d8d71d7 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -1400,7 +1400,7 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
 		/*
 		 * If there are any securityQuals on the RTE from security barrier
 		 * views or RLS policies, then the user may not have access to all the
-		 * table's data, and we must check that the operator is leak-proof.
+		 * table's data, and we must check that the operator is leakproof.
 		 *
 		 * If the operator is leaky, then we must ignore this clause for the
 		 * purposes of estimating with MCV lists, otherwise the operator might
@@ -1467,7 +1467,7 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
 		/*
 		 * If there are any securityQuals on the RTE from security barrier
 		 * views or RLS policies, then the user may not have access to all the
-		 * table's data, and we must check that the operator is leak-proof.
+		 * table's data, and we must check that the operator is leakproof.
 		 *
 		 * If the operator is leaky, then we must ignore this clause for the
 		 * purposes of estimating with MCV lists, otherwise the operator might
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 08fa6774d9..eb6cbd2b47 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5740,7 +5740,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
  * Check whether it is permitted to call func_oid passing some of the
  * pg_statistic data in vardata.  We allow this either if the user has SELECT
  * privileges on the table or column underlying the pg_statistic data or if
- * the function is marked leak-proof.
+ * the function is marked leakproof.
  */
 bool
 statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
@@ -5755,7 +5755,7 @@ statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
 		return true;
 
 	ereport(DEBUG2,
-			(errmsg_internal("not using statistics because function \"%s\" is not leak-proof",
+			(errmsg_internal("not using statistics because function \"%s\" is not leakproof",
 							 get_func_name(func_oid))));
 	return false;
 }
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 51f4309241..3b4763b954 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -61,7 +61,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
 	/* security definer */
 	bool		prosecdef BKI_DEFAULT(f);
 
-	/* is it a leak-proof function? */
+	/* is it a leakproof function? */
 	bool		proleakproof BKI_DEFAULT(f);
 
 	/* strict with respect to NULLs? */
-- 
2.34.1

>From c28a32aa619b4f07fd80b0017ff5930ef711831e Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email-masked]>
Date: Mon, 1 Jul 2024 16:16:39 +0900
Subject: [PATCH v3 1/2] psql: Add leakproof field to \dAo+ meta-command
 results

This adds a field that shows whether the underlying function of an
operator associated with operator families is leak-proof.
It is useful for checking an index can be used with security_barrier
views or row-level security policies when the query's WHERE
clause contains an operator which is associated with the index.
---
 doc/src/sgml/planstats.sgml        |  2 +
 doc/src/sgml/ref/psql-ref.sgml     | 16 ++++---
 doc/src/sgml/rules.sgml            | 10 ++++
 src/bin/psql/describe.c            | 47 +++++++++++++++----
 src/test/regress/expected/psql.out | 75 ++++++++++++++++--------------
 src/test/regress/sql/psql.sql      |  2 +-
 6 files changed, 99 insertions(+), 53 deletions(-)

diff --git a/doc/src/sgml/planstats.sgml b/doc/src/sgml/planstats.sgml
index c7ec749d0a..c2a7142453 100644
--- a/doc/src/sgml/planstats.sgml
+++ b/doc/src/sgml/planstats.sgml
@@ -729,6 +729,8 @@ EXPLAIN (ANALYZE, TIMING OFF) SELECT * FROM t WHERE a &lt;= 49 AND b &gt; 49;
    accurately, the function that the operator is based on).  If not, then the
    selectivity estimator will behave as if no statistics are available, and
    the planner will proceed with default or fall-back assumptions.
+   Use <xref linkend="app-psql"/>'s <command>\do+</command> command to list
+   operators and tell which are marked as leakproof.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index e42073ed74..ef5e366b1d 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1420,7 +1420,8 @@ SELECT $1 \parse stmt1
         is specified, only members of operator families whose names match that
         pattern are listed.
         If <literal>+</literal> is appended to the command name, each operator
-        is listed with its sort operator family (if it is an ordering operator).
+        is listed with its sort operator family (if it is an ordering operator),
+        and whether it is leakproof.
         </para>
         </listitem>
       </varlistentry>
@@ -1510,7 +1511,8 @@ SELECT $1 \parse stmt1
         is specified, only casts whose source or target types match the
         pattern are listed.
         If <literal>+</literal> is appended to the command name, each object
-        is listed with its associated description.
+        is listed with whether the underlying function is leakproof and
+        its associated description.
         </para>
         </listitem>
       </varlistentry>
@@ -1711,9 +1713,9 @@ SELECT $1 \parse stmt1
         modifier to include system objects.
         If the form <literal>\df+</literal> is used, additional information
         about each function is shown, including volatility,
-        parallel safety, owner, security classification, access privileges,
-        language, internal name (for C and internal functions only),
-        and description.
+        parallel safety, owner, security classification, whether it is
+        leakproof, access privileges, language, internal name (for C and
+        internal functions only), and description.
         Source code for a specific function can be seen
         using <literal>\sf</literal>.
         </para>
@@ -1862,8 +1864,8 @@ SELECT $1 \parse stmt1
         pattern or the <literal>S</literal> modifier to include system
         objects.
         If <literal>+</literal> is appended to the command name,
-        additional information about each operator is shown, currently just
-        the name of the underlying function.
+        additional information about each operator is shown, currently
+        the name of the underlying function and whether it is leakproof.
         </para>
         </listitem>
       </varlistentry>
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 7a928bd7b9..74cd1d29fd 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -2167,6 +2167,16 @@ CREATE VIEW phone_number WITH (security_barrier) AS
     view's row filters.
 </para>
 
+<para>
+    For example, an index scan cannot be selected for queries with
+    <literal>security_barrier</literal> views or row-level security policies if an
+    operator used in the <literal>WHERE</literal> clause is associated with the
+    operator family of the index, but its underlying function is not marked
+    <literal>LEAKPROOF</literal>. Use <xref linkend="app-psql"/>'s
+    <command>\dAo+</command> command to list operator families and tell which of
+    their operators are marked as leakproof.
+</para>
+
 <para>
     It is important to understand that even a view created with the
     <literal>security_barrier</literal> option is intended to be secure only
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 2657abdc72..a91c2855e6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -304,7 +304,7 @@ describeFunctions(const char *functypes, const char *func_pattern,
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, false, false, false, false, false};
 
 	/* No "Parallel" column before 9.6 */
 	static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, false, false, false, false};
@@ -409,11 +409,15 @@ describeFunctions(const char *functypes, const char *func_pattern,
 							  gettext_noop("Parallel"));
 		appendPQExpBuffer(&buf,
 						  ",\n pg_catalog.pg_get_userbyid(p.proowner) as \"%s\""
-						  ",\n CASE WHEN prosecdef THEN '%s' ELSE '%s' END AS \"%s\"",
+						  ",\n CASE WHEN prosecdef THEN '%s' ELSE '%s' END AS \"%s\""
+						  ",\n CASE WHEN p.proleakproof THEN '%s' ELSE '%s' END as \"%s\"",
 						  gettext_noop("Owner"),
 						  gettext_noop("definer"),
 						  gettext_noop("invoker"),
-						  gettext_noop("Security"));
+						  gettext_noop("Security"),
+						  gettext_noop("yes"),
+						  gettext_noop("no"),
+						  gettext_noop("Leakproof"));
 		appendPQExpBufferStr(&buf, ",\n ");
 		printACLColumn(&buf, "p.proacl");
 		appendPQExpBuffer(&buf,
@@ -825,8 +829,12 @@ describeOperators(const char *oper_pattern,
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
-						  "  o.oprcode AS \"%s\",\n",
-						  gettext_noop("Function"));
+						  "  o.oprcode AS \"%s\",\n"
+						  "  CASE WHEN p.proleakproof THEN '%s' ELSE '%s' END AS \"%s\",\n",
+						  gettext_noop("Function"),
+						  gettext_noop("yes"),
+						  gettext_noop("no"),
+						  gettext_noop("Leakproof"));
 
 	appendPQExpBuffer(&buf,
 					  "  coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n"
@@ -851,6 +859,10 @@ describeOperators(const char *oper_pattern,
 							 "     LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n");
 	}
 
+	if (verbose)
+		appendPQExpBufferStr(&buf,
+							 "     LEFT JOIN pg_catalog.pg_proc p ON p.oid = o.oprcode\n");
+
 	if (!showSystem && !oper_pattern)
 		appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
 							 "      AND n.nspname <> 'information_schema'\n");
@@ -4924,7 +4936,13 @@ listCasts(const char *pattern, bool verbose)
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
-						  ",\n       d.description AS \"%s\"",
+						  ",\n       CASE WHEN p.proleakproof THEN '%s'\n"
+						  "            ELSE '%s'\n"
+						  "       END AS \"%s\",\n"
+						  "       d.description AS \"%s\"",
+						  gettext_noop("yes"),
+						  gettext_noop("no"),
+						  gettext_noop("Leakproof"),
 						  gettext_noop("Description"));
 
 	/*
@@ -6987,7 +7005,7 @@ listOpFamilyOperators(const char *access_method_pattern,
 	printQueryOpt myopt = pset.popt;
 	bool		have_where = false;

 	initPQExpBuffer(&buf);
 
@@ -7015,8 +7033,15 @@ listOpFamilyOperators(const char *access_method_pattern,
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
+						  gettext_noop("Leakproof"));
@@ -7024,7 +7049,9 @@ listOpFamilyOperators(const char *access_method_pattern,
 
 	if (access_method_pattern)
 	{
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 36dc31c16c..06d6750abd 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5184,31 +5184,36 @@ List of access methods
  btree | integer_ops     | smallint, integer, bigint
 (1 row)
 
-\dAo+ btree float_ops
+  AM   | Operator family |               Operator                | Strategy | Purpose | Sort opfamily | Leakproof 
+-------+-----------------+---------------------------------------+----------+---------+---------------+-----------
 
 \dAo * pg_catalog.jsonb_path_ops
              List of operators of operator families
@@ -5388,12 +5393,12 @@ create function psql_df_plpgsql ()
   as $$ begin return; end; $$;
 comment on function psql_df_plpgsql () is 'some comment';
 \df+ psql_df_*
-                                                                                       List of functions
- Schema |       Name       | Result data type | Argument data types | Type | Volatility | Parallel |       Owner       | Security | Access privileges | Language | Internal name | Description  
---------+------------------+------------------+---------------------+------+------------+----------+-------------------+----------+-------------------+----------+---------------+--------------
- public | psql_df_internal | double precision | double precision    | func | immutable  | safe     | regress_psql_user | invoker  |                   | internal | dsin          | 
- public | psql_df_plpgsql  | void             |                     | func | volatile   | unsafe   | regress_psql_user | invoker  |                   | plpgsql  |               | some comment
- public | psql_df_sql      | integer          | x integer           | func | volatile   | unsafe   | regress_psql_user | definer  |                   | sql      |               | 
+                                                                                             List of functions
+ Schema |       Name       | Result data type | Argument data types | Type | Volatility | Parallel |       Owner       | Security | Leakproof | Access privileges | Language | Internal name | Description  
+--------+------------------+------------------+---------------------+------+------------+----------+-------------------+----------+-----------+-------------------+----------+---------------+--------------
+ public | psql_df_internal | double precision | double precision    | func | immutable  | safe     | regress_psql_user | invoker  | no        |                   | internal | dsin          | 
+ public | psql_df_plpgsql  | void             |                     | func | volatile   | unsafe   | regress_psql_user | invoker  | no        |                   | plpgsql  |               | some comment
+ public | psql_df_sql      | integer          | x integer           | func | volatile   | unsafe   | regress_psql_user | definer  | no        |                   | sql      |               | 
 (3 rows)
 
 rollback;
@@ -6791,10 +6796,10 @@ REVOKE ALL ON DOMAIN regress_zeropriv_domain FROM CURRENT_USER, PUBLIC;
 CREATE PROCEDURE regress_zeropriv_proc() LANGUAGE sql AS '';
 REVOKE ALL ON PROCEDURE regress_zeropriv_proc() FROM CURRENT_USER, PUBLIC;
 \df+ regress_zeropriv_proc
-                                                                                            List of functions
- Schema |         Name          | Result data type | Argument data types | Type | Volatility | Parallel |         Owner          | Security | Access privileges | Language | Internal name | Description 
---------+-----------------------+------------------+---------------------+------+------------+----------+------------------------+----------+-------------------+----------+---------------+-------------
- public | regress_zeropriv_proc |                  |                     | proc | volatile   | unsafe   | regress_zeropriv_owner | invoker  | (none)            | sql      |               | 
+                                                                                                  List of functions
+ Schema |         Name          | Result data type | Argument data types | Type | Volatility | Parallel |         Owner          | Security | Leakproof | Access privileges | Language | Internal name | Description 
+--------+-----------------------+------------------+---------------------+------+------------+----------+------------------------+----------+-----------+-------------------+----------+---------------+-------------
+ public | regress_zeropriv_proc |                  |                     | proc | volatile   | unsafe   | regress_zeropriv_owner | invoker  | no        | (none)            | sql      |               | 
 (1 row)
 
 CREATE TABLE regress_zeropriv_tbl (a int);
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index c5021fc0b1..f6c5aa1f8b 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1306,7 +1306,7 @@ drop role regress_partitioning_role;
 \dAc brin pg*.oid*
 \dAf spgist
 \dAf btree int4
-\dAo+ btree float_ops
+\dAo+ btree array_ops|float_ops
 \dAo * pg_catalog.jsonb_path_ops
 \dAp+ btree float_ops
 \dAp * pg_catalog.uuid_ops
-- 
2.34.1

---

From: Dean Rasheed <[email-masked]>
Date: Fri, 10 Jan 2025 11:31:39 +0000

> I've attached a updated patch (v3-0001) that include changes on all

Nice. I think this is very useful.

I spotted one issue, which can be seen by compiling with --enable-nls
and --enable-cassert. In that case \dC+ fails with an assertion error:

\dC+ json
psql: print.c:3564: printQuery: Assertion `opt->translate_columns ==
((void *)0) || opt->n_translate_columns >= cont.ncolumns' failed.
Aborted (core dumped)

This is because translate_columns[] in listCasts() needs to be updated.

Similarly, in describeFunctions(), translate_columns_pre_96[] needs to
be updated to support connecting to pre-9.6 servers.

The translate_columns entries for this new column should be true, so
that the "yes"/"no" gets appropriately translated. That means that
describeOperators() will need a similar translate_columns array.

Regards,
Dean

---

From: Yugo NAGATA <[email-masked]>
Date: Tue, 14 Jan 2025 17:44:08 +0900

On Fri, 10 Jan 2025 11:31:39 +0000

> describeOperators() will need a similar translate_columns array.

Thank you for pointing out this.
I've attached a updated patch v4 that includes fixes on translate_columns[]
and ranslate_columns_pre_96[].

Regards,
Yugo Nagata

>From 6e889e6ed4884cebb2c86cb3798ff35d9a20f67f Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email-masked]>
Date: Wed, 4 Dec 2024 20:14:21 +0900
Subject: [PATCH v4 2/2] Doc: replace "leak-proof" in documents and comments
 with "leakproof"

---

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 238ed67919..d3036c5ba9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -6040,7 +6040,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        The function has no side effects.  No information about the
        arguments is conveyed except via the return value.  Any function
        that might throw an error depending on the values of its arguments
-       is not leak-proof.
+       is not leakproof.
       </para></entry>
      </row>
 
index de30ac645e..b670d0af4d 100644
@@ -739,7 +739,7 @@ EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a &lt;= 49 AND
index 34dcb535e1..a8b63ec088 100644
@@ -1397,7 +1397,7 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
@@ -1464,7 +1464,7 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
index 93e4a8906c..d3d1e485bb 100644
@@ -5763,7 +5763,7 @@ examine_simple_variable(PlannerInfo *root, Var *var,
@@ -5778,7 +5778,7 @@ statistic_proc_security_check(VariableStatData *vardata, Oid func_oid)
 		return true;
 
 	ereport(DEBUG2,
index fdb1e28071..b34b00aa3e 100644

>From bde576e08bb761d367ddf748115f17648d53defe Mon Sep 17 00:00:00 2001
From: Yugo Nagata <[email-masked]>
Date: Mon, 1 Jul 2024 16:16:39 +0900
Subject: [PATCH v4 1/2] psql: Add leakproof field to \df+, \do+, \dAo+, and
 \dC+ meta-command results

This allows users to know whether a functions itself, or a function underlying
an operator or a cast is leak-proof. This is useful for checking if an index
can be used for a condition in a WHERE clause containing a function, an operator,
or a cast when security_barrier views or row-level security policies is used.
---
 doc/src/sgml/planstats.sgml        |  2 +
 doc/src/sgml/ref/psql-ref.sgml     | 16 ++++---
 doc/src/sgml/rules.sgml            | 10 ++++
 src/bin/psql/describe.c            | 54 ++++++++++++++++-----
 src/test/regress/expected/psql.out | 75 ++++++++++++++++--------------
 src/test/regress/sql/psql.sql      |  2 +-
 6 files changed, 104 insertions(+), 55 deletions(-)

index c957f4f362..de30ac645e 100644
@@ -729,6 +729,8 @@ EXPLAIN (ANALYZE, TIMING OFF, BUFFERS OFF) SELECT * FROM t WHERE a &lt;= 49 AND
 
   <para>
index 72f3347e53..4ab9627c57 100644
 
+<para>
index d5543fd62b..448f3d857f 100644
@@ -304,10 +304,10 @@ describeFunctions(const char *functypes, const char *func_pattern,
+	static const bool translate_columns[] = {false, false, false, false, true, true, true, false, true, true, false, false, false, false};
 
 	/* No "Parallel" column before 9.6 */
-	static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, false, false, false, false};
+	static const bool translate_columns_pre_96[] = {false, false, false, false, true, true, false, true, true, false, false, false, false};
 
 	if (strlen(functypes) != strspn(functypes, "anptwS+"))
 	{
@@ -792,6 +796,7 @@ describeOperators(const char *oper_pattern,
+	static const bool translate_columns[] = {false, false, false, false, false, false, true, false};
 
 	initPQExpBuffer(&buf);
 
@@ -825,8 +830,12 @@ describeOperators(const char *oper_pattern,
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
 
 	appendPQExpBuffer(&buf,
 					  "  coalesce(pg_catalog.obj_description(o.oid, 'pg_operator'),\n"
@@ -851,6 +860,10 @@ describeOperators(const char *oper_pattern,
 							 "     LEFT JOIN pg_catalog.pg_namespace nt0 ON nt0.oid = t0.typnamespace\n");
 	}
 
+	if (verbose)
@@ -908,6 +921,8 @@ describeOperators(const char *oper_pattern,
 
 	myopt.title = _("List of operators");
 	myopt.translate_header = true;
+	myopt.translate_columns = translate_columns;
+	myopt.n_translate_columns = lengthof(translate_columns);
 
 	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
 
@@ -4886,7 +4901,7 @@ listCasts(const char *pattern, bool verbose)
 	PQExpBufferData buf;
 	PGresult   *res;
 	printQueryOpt myopt = pset.popt;
-	static const bool translate_columns[] = {false, false, false, true, false};
+	static const bool translate_columns[] = {false, false, false, true, true, false};
 
 	initPQExpBuffer(&buf);
 
@@ -4924,7 +4939,13 @@ listCasts(const char *pattern, bool verbose)
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
 
 	/*
@@ -6987,7 +7008,7 @@ listOpFamilyOperators(const char *access_method_pattern,
 	printQueryOpt myopt = pset.popt;
 	bool		have_where = false;
 
-	static const bool translate_columns[] = {false, false, false, false, false, false};
+	static const bool translate_columns[] = {false, false, false, false, false, false, true};
 
 	initPQExpBuffer(&buf);
 
@@ -7015,8 +7036,15 @@ listOpFamilyOperators(const char *access_method_pattern,
 
 	if (verbose)
 		appendPQExpBuffer(&buf,
@@ -7024,7 +7052,9 @@ listOpFamilyOperators(const char *access_method_pattern,

-\dAo+ btree float_ops

 rollback;

---

From: Dean Rasheed <[email-masked]>
Date: Tue, 14 Jan 2025 13:58:18 +0000

> and ranslate_columns_pre_96[].

This looked good to me, so I've pushed both patches.

I changed the column name to "Leakproof?" with a question mark,
because all other boolean columns in psql meta-commands end with a
question mark.

Regards,
Dean

---

From: Yugo NAGATA <[email-masked]>
Date: Wed, 15 Jan 2025 01:21:41 +0900

On Tue, 14 Jan 2025 13:58:18 +0000

> because all other boolean columns in psql meta-commands end with a

Thank you!

Regards,
Yugo Nagata