From 8c730c6b51a0d39762b5518b17c0a77e41a1e403 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 2 Sep 2020 07:43:27 -0700
Subject: [PATCH v7 1/3] Removing postfix operator support.

Removing postfix operator support from the grammar; disallowing
the creation of postfix operators through the CREATE OPERATOR
command; and removing the factorial prefix and postfix operators.

In the commit message for 6ca547cf75ef6e922476c51a3fb5e253eef5f1b6,
Tom Lane wrote:

    There is only one built-in postfix operator, ! for factorial.
    Label it deprecated in the docs and in pg_description, and
    adjust some examples that formerly relied on it.  (The sister
    prefix operator !! is also deprecated.  We don't really have to
    remove that one, but since we're suggesting that people use
    factorial() instead, it seems better to remove both operators.)

In the spirit of that message, this patch also removes the !!
factorial prefix operator, even though it is not strongly connected
with the rest of what this commit does.

This accomplishes nothing useful in itself, but sets up for grammar
changes to allow keywords to be used as bare column aliases without
the use of an explicit "AS".
---
 contrib/postgres_fdw/deparse.c                |   5 +-
 .../postgres_fdw/expected/postgres_fdw.out    |   8 --
 contrib/postgres_fdw/sql/postgres_fdw.sql     |   1 -
 doc/src/sgml/catalogs.sgml                    |   2 +-
 doc/src/sgml/func.sgml                        |  30 -----
 doc/src/sgml/ref/alter_extension.sgml         |   2 +-
 doc/src/sgml/ref/alter_opfamily.sgml          |   2 +-
 doc/src/sgml/ref/comment.sgml                 |   2 +-
 doc/src/sgml/ref/create_opclass.sgml          |   2 +-
 doc/src/sgml/ref/create_operator.sgml         |  20 +--
 doc/src/sgml/ref/drop_operator.sgml           |   7 -
 doc/src/sgml/syntax.sgml                      |   3 +-
 doc/src/sgml/typeconv.sgml                    |   2 +-
 doc/src/sgml/xoper.sgml                       |  12 +-
 src/backend/catalog/namespace.c               |   7 +-
 src/backend/catalog/pg_operator.c             |   4 +-
 src/backend/commands/operatorcmds.c           |  14 +-
 src/backend/nodes/print.c                     |   1 -
 src/backend/parser/gram.y                     |  30 ++---
 src/backend/parser/parse_expr.c               |  48 ++-----
 src/backend/parser/parse_oper.c               | 121 +++---------------
 src/backend/utils/adt/ruleutils.c             |  31 ++---
 src/bin/pg_dump/pg_dump.c                     |   5 +
 src/bin/pg_dump/pg_dump_sort.c                |   2 +-
 src/bin/pg_upgrade/check.c                    | 106 +++++++++++++++
 src/bin/psql/describe.c                       |   2 +-
 src/include/catalog/pg_operator.dat           |   6 -
 src/include/catalog/pg_operator.h             |   4 +-
 src/include/catalog/pg_proc.dat               |   4 -
 src/test/regress/expected/create_operator.out |  33 +++--
 src/test/regress/expected/numeric.out         |  35 +++--
 src/test/regress/expected/opr_sanity.out      |  17 +--
 src/test/regress/sql/create_operator.sql      |  15 ++-
 src/test/regress/sql/numeric.sql              |  13 +-
 src/test/regress/sql/opr_sanity.sql           |  14 +-
 src/tutorial/complex.source                   |   2 +-
 src/tutorial/syscat.source                    |  14 --
 37 files changed, 257 insertions(+), 369 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index ad37a74221..32a9b38f10 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2716,15 +2716,14 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context)
 	oprkind = form->oprkind;
 
 	/* Sanity check. */
-	Assert((oprkind == 'r' && list_length(node->args) == 1) ||
-		   (oprkind == 'l' && list_length(node->args) == 1) ||
+	Assert((oprkind == 'l' && list_length(node->args) == 1) ||
 		   (oprkind == 'b' && list_length(node->args) == 2));
 
 	/* Always parenthesize the expression. */
 	appendStringInfoChar(buf, '(');
 
 	/* Deparse left operand. */
-	if (oprkind == 'r' || oprkind == 'b')
+	if (oprkind == 'b')
 	{
 		arg = list_head(node->args);
 		deparseExpr(lfirst(arg), context);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 90db550b92..cae90b45e2 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -653,14 +653,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- Op
    Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = (- "C 1")))
 (3 rows)
 
-EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
-                                                QUERY PLAN                                                
-----------------------------------------------------------------------------------------------------------
- Foreign Scan on public.ft1 t1
-   Output: c1, c2, c3, c4, c5, c6, c7, c8
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((1::numeric = ("C 1" !)))
-(3 rows)
-
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
                                                                  QUERY PLAN                                                                 
 --------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 83971665e3..bbad5b6ec2 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -307,7 +307,6 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NULL;        -- Nu
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL;    -- NullTest
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- OpExpr(l)
-EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c1 = (ARRAY[c1,c2,3])[1]; -- SubscriptingRef
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 1d1b8ce8fb..39eee9d1e2 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5154,7 +5154,7 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       </para>
       <para>
        <literal>b</literal> = infix (<quote>both</quote>), <literal>l</literal> = prefix
-       (<quote>left</quote>), <literal>r</literal> = postfix (<quote>right</quote>)
+       (<quote>left</quote>)
       </para></entry>
      </row>
 
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 2efd80baa4..8888bc73c9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1048,36 +1048,6 @@ repeat('Pg', 4) <returnvalue>PgPgPgPg</returnvalue>
        </para></entry>
       </row>
 
-      <row>
-       <entry role="func_table_entry"><para role="func_signature">
-        <type>bigint</type> <literal>!</literal>
-        <returnvalue>numeric</returnvalue>
-       </para>
-       <para>
-        Factorial
-        (deprecated, use <link linkend="function-factorial"><function>factorial()</function></link> instead)
-       </para>
-       <para>
-        <literal>5 !</literal>
-        <returnvalue>120</returnvalue>
-       </para></entry>
-      </row>
-
-      <row>
-       <entry role="func_table_entry"><para role="func_signature">
-        <literal>!!</literal> <type>bigint</type>
-        <returnvalue>numeric</returnvalue>
-       </para>
-       <para>
-        Factorial as a prefix operator
-        (deprecated, use <link linkend="function-factorial"><function>factorial()</function></link> instead)
-       </para>
-       <para>
-        <literal>!! 5</literal>
-        <returnvalue>120</returnvalue>
-       </para></entry>
-      </row>
-
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <literal>@</literal> <replaceable>numeric_type</replaceable>
diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml
index a2d405d6cd..c819c7bb4e 100644
--- a/doc/src/sgml/ref/alter_extension.sgml
+++ b/doc/src/sgml/ref/alter_extension.sgml
@@ -251,7 +251,7 @@ ALTER EXTENSION <replaceable class="parameter">name</replaceable> DROP <replacea
       <para>
        The data type(s) of the operator's arguments (optionally
        schema-qualified).  Write <literal>NONE</literal> for the missing argument
-       of a prefix or postfix operator.
+       of a prefix operator.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/ref/alter_opfamily.sgml b/doc/src/sgml/ref/alter_opfamily.sgml
index 4ac1cca95a..c86507edac 100644
--- a/doc/src/sgml/ref/alter_opfamily.sgml
+++ b/doc/src/sgml/ref/alter_opfamily.sgml
@@ -141,7 +141,7 @@ ALTER OPERATOR FAMILY <replaceable>name</replaceable> USING <replaceable class="
      <para>
       In an <literal>OPERATOR</literal> clause,
       the operand data type(s) of the operator, or <literal>NONE</literal> to
-      signify a left-unary or right-unary operator.  Unlike the comparable
+      signify a left-unary operator.  Unlike the comparable
       syntax in <command>CREATE OPERATOR CLASS</command>, the operand data types
       must always be specified.
      </para>
diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml
index fd7492a255..6e8ced3eaf 100644
--- a/doc/src/sgml/ref/comment.sgml
+++ b/doc/src/sgml/ref/comment.sgml
@@ -224,7 +224,7 @@ COMMENT ON
      <para>
       The data type(s) of the operator's arguments (optionally
       schema-qualified).  Write <literal>NONE</literal> for the missing argument
-      of a prefix or postfix operator.
+      of a prefix operator.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/create_opclass.sgml b/doc/src/sgml/ref/create_opclass.sgml
index f42fb6494c..0f94b6ae3e 100644
--- a/doc/src/sgml/ref/create_opclass.sgml
+++ b/doc/src/sgml/ref/create_opclass.sgml
@@ -161,7 +161,7 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
      <para>
       In an <literal>OPERATOR</literal> clause,
       the operand data type(s) of the operator, or <literal>NONE</literal> to
-      signify a left-unary or right-unary operator.  The operand data
+      signify a left-unary operator.  The operand data
       types can be omitted in the normal case where they are the same
       as the operator class's data type.
      </para>
diff --git a/doc/src/sgml/ref/create_operator.sgml b/doc/src/sgml/ref/create_operator.sgml
index 66c34e0072..082494bd75 100644
--- a/doc/src/sgml/ref/create_operator.sgml
+++ b/doc/src/sgml/ref/create_operator.sgml
@@ -86,20 +86,9 @@ CREATE OPERATOR <replaceable>name</replaceable> (
   </para>
 
   <para>
-   At least one of <literal>LEFTARG</literal> and <literal>RIGHTARG</literal> must be defined.  For
-   binary operators, both must be defined. For right unary
-   operators, only <literal>LEFTARG</literal> should be defined, while for left
-   unary operators only <literal>RIGHTARG</literal> should be defined.
-  </para>
-
-  <note>
-   <para>
-    Right unary, also called postfix, operators are deprecated and will be
-    removed in <productname>PostgreSQL</productname> version 14.
-   </para>
-  </note>
-
-  <para>
+   For binary operators, both <literal>LEFTARG</literal> and
+   <literal>RIGHTARG</literal> must be defined.  For left unary operators only
+   <literal>RIGHTARG</literal> should be defined.
    The <replaceable class="parameter">function_name</replaceable>
    function must have been previously defined using <command>CREATE
    FUNCTION</command> and must be defined to accept the correct number
@@ -169,8 +158,7 @@ CREATE OPERATOR <replaceable>name</replaceable> (
       <term><replaceable class="parameter">right_type</replaceable></term>
       <listitem>
        <para>
-        The data type of the operator's right operand, if any.
-        This option would be omitted for a right-unary operator.
+        The data type of the operator's right operand.
        </para>
       </listitem>
      </varlistentry>
diff --git a/doc/src/sgml/ref/drop_operator.sgml b/doc/src/sgml/ref/drop_operator.sgml
index 2dff050ecf..3c8eecb53a 100644
--- a/doc/src/sgml/ref/drop_operator.sgml
+++ b/doc/src/sgml/ref/drop_operator.sgml
@@ -120,13 +120,6 @@ DROP OPERATOR ~ (none, bit);
 </programlisting>
   </para>
 
-  <para>
-   Remove the right unary factorial operator <literal>x!</literal>
-   for type <type>bigint</type>:
-<programlisting>
-DROP OPERATOR ! (bigint, none);
-</programlisting></para>
-
   <para>
    Remove multiple operators in one command:
 <programlisting>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index b0ae5d2e12..abde93895a 100644
--- a/doc/src/sgml/syntax.sgml
+++ b/doc/src/sgml/syntax.sgml
@@ -1444,11 +1444,10 @@ $1.somecolumn
    </indexterm>
 
    <para>
-    There are three possible syntaxes for an operator invocation:
+    There are two possible syntaxes for an operator invocation:
     <simplelist>
      <member><replaceable>expression</replaceable> <replaceable>operator</replaceable> <replaceable>expression</replaceable> (binary infix operator)</member>
      <member><replaceable>operator</replaceable> <replaceable>expression</replaceable> (unary prefix operator)</member>
-     <member><replaceable>expression</replaceable> <replaceable>operator</replaceable> (unary postfix operator)</member>
     </simplelist>
     where the <replaceable>operator</replaceable> token follows the syntax
     rules of <xref linkend="sql-syntax-operators"/>, or is one of the
diff --git a/doc/src/sgml/typeconv.sgml b/doc/src/sgml/typeconv.sgml
index 98662fc91f..ef9e311342 100644
--- a/doc/src/sgml/typeconv.sgml
+++ b/doc/src/sgml/typeconv.sgml
@@ -97,7 +97,7 @@ Operators
 <listitem>
 <para>
 <productname>PostgreSQL</productname> allows expressions with
-prefix and postfix unary (one-argument) operators,
+prefix unary (one-argument) operators,
 as well as binary (two-argument) operators.  Like functions, operators can
 be overloaded, so the same problem of selecting the right operator
 exists.
diff --git a/doc/src/sgml/xoper.sgml b/doc/src/sgml/xoper.sgml
index 56b08491c9..585a8c958f 100644
--- a/doc/src/sgml/xoper.sgml
+++ b/doc/src/sgml/xoper.sgml
@@ -20,8 +20,8 @@
   </para>
 
   <para>
-   <productname>PostgreSQL</productname> supports left unary, right
-   unary, and binary operators.  Operators can be
+   <productname>PostgreSQL</productname> supports left unary
+   and binary operators.  Operators can be
    overloaded;<indexterm><primary>overloading</primary><secondary>operators</secondary></indexterm>
    that is, the same operator name can be used for different operators
    that have different numbers and types of operands.  When a query is
@@ -64,9 +64,9 @@ SELECT (a + b) AS c FROM test_complex;
   </para>
 
   <para>
-   We've shown how to create a binary operator here.  To create unary
-   operators, just omit one of <literal>leftarg</literal> (for left unary) or
-   <literal>rightarg</literal> (for right unary).  The <literal>function</literal>
+   We've shown how to create a binary operator here.  To create left unary
+   operators, just omit the <literal>leftarg</literal>.
+   The <literal>function</literal>
    clause and the argument clauses are the only required items in
    <command>CREATE OPERATOR</command>.  The <literal>commutator</literal>
    clause shown in the example is an optional hint to the query
@@ -202,7 +202,7 @@ SELECT (a + b) AS c FROM test_complex;
    <para>
     Unlike commutators, a pair of unary operators could validly be marked
     as each other's negators; that would mean (A x) equals NOT (B x)
-    for all x, or the equivalent for right unary operators.
+    for all x.
    </para>
 
    <para>
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 0152e3869a..0c78ab8af7 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1473,8 +1473,7 @@ FunctionIsVisible(Oid funcid)
  *		Given a possibly-qualified operator name and exact input datatypes,
  *		look up the operator.  Returns InvalidOid if not found.
  *
- * Pass oprleft = InvalidOid for a prefix op, oprright = InvalidOid for
- * a postfix op.
+ * Pass oprleft = InvalidOid for a prefix op.
  *
  * If the operator name is not schema-qualified, it is sought in the current
  * namespace search path.  If the name is schema-qualified and the given
@@ -1580,8 +1579,8 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
  * namespace case, we arrange for entries in earlier namespaces to mask
  * identical entries in later namespaces.
  *
- * The returned items always have two args[] entries --- one or the other
- * will be InvalidOid for a prefix or postfix oprkind.  nargs is 2, too.
+ * The returned items always have two args[] entries --- one will be
+ * InvalidOid for a prefix oprkind.  nargs is 2, too.
  */
 FuncCandidateList
 OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 65a36be5ee..f817627d86 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -245,7 +245,7 @@ OperatorShellMake(const char *operatorName,
 	values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname);
 	values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace);
 	values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId());
-	values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? (rightTypeId ? 'b' : 'r') : 'l');
+	values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l');
 	values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(false);
 	values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(false);
 	values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId);
@@ -494,7 +494,7 @@ OperatorCreate(const char *operatorName,
 	values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname);
 	values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace);
 	values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId());
-	values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? (rightTypeId ? 'b' : 'r') : 'l');
+	values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l');
 	values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(canMerge);
 	values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash);
 	values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId);
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index bf23937849..b468c7701b 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -168,10 +168,22 @@ DefineOperator(List *names, List *parameters)
 	if (typeName2)
 		typeId2 = typenameTypeId(NULL, typeName2);
 
+	/*
+	 * If only the right argument is missing, the user is likely trying to
+	 * create a postfix operator, so give them a hint about why that does not
+	 * work.  But if both arguments are missing, do not mention postfix
+	 * operators, as the user most likely simply neglected to mention the
+	 * arguments.
+	 */
 	if (!OidIsValid(typeId1) && !OidIsValid(typeId2))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
-				 errmsg("at least one of leftarg or rightarg must be specified")));
+				 errmsg("operator arguments must be specified")));
+	if (!OidIsValid(typeId2))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+				 errmsg("operator right argument must be specified"),
+				 errhint("Postfix operators are not supported.")));
 
 	if (typeName1)
 	{
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 42476724d8..970a2d4384 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -394,7 +394,6 @@ print_expr(const Node *expr, const List *rtable)
 		}
 		else
 		{
-			/* we print prefix and postfix ops the same... */
 			printf("%s ", ((opname != NULL) ? opname : "(invalid operator)"));
 			print_expr(get_leftop((const Expr *) e), rtable);
 		}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4982..d8a607e29a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -741,19 +741,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
-%left		POSTFIXOP		/* dummy for postfix Op rules */
 /*
  * To support target_el without AS, we must give IDENT an explicit priority
- * between POSTFIXOP and Op.  We can safely assign the same priority to
- * various unreserved keywords as needed to resolve ambiguities (this can't
- * have any bad effects since obviously the keywords will still behave the
- * same as if they weren't keywords).  We need to do this:
+ * lower than Op.  We can safely assign the same priority to various
+ * unreserved keywords as needed to resolve ambiguities (this can't have any
+ * bad effects since obviously the keywords will still behave the same as if
+ * they weren't keywords).  We need to do this:
  * for PARTITION, RANGE, ROWS, GROUPS to support opt_existing_window_name;
- * for RANGE, ROWS, GROUPS so that they can follow a_expr without creating
- * postfix-operator problems;
  * for GENERATED so that it can follow b_expr;
- * and for NULL so that it can follow b_expr in ColQualList without creating
- * postfix-operator problems.
  *
  * To support CUBE and ROLLUP in GROUP BY without reserving them, we give them
  * an explicit priority lower than '(', so that a rule with CUBE '(' will shift
@@ -7993,7 +7988,12 @@ oper_argtypes:
 			| '(' NONE ',' Typename ')'					/* left unary */
 					{ $$ = list_make2(NULL, $4); }
 			| '(' Typename ',' NONE ')'					/* right unary */
-					{ $$ = list_make2($2, NULL); }
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+							 errmsg("operator right argument must be specified"),
+							 errhint("Postfix operators are not supported.")));
+				}
 		;
 
 any_operator:
@@ -12989,8 +12989,6 @@ a_expr:		c_expr									{ $$ = $1; }
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
 			| qual_Op a_expr					%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $1, NULL, $2, @1); }
-			| a_expr qual_Op					%prec POSTFIXOP
-				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, NULL, @2); }
 
 			| a_expr AND a_expr
 				{ $$ = makeAndExpr($1, $3, @2); }
@@ -13404,8 +13402,6 @@ b_expr:		c_expr
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
 			| qual_Op b_expr					%prec Op
 				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $1, NULL, $2, @1); }
-			| b_expr qual_Op					%prec POSTFIXOP
-				{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, NULL, @2); }
 			| b_expr IS DISTINCT FROM b_expr		%prec IS
 				{
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_DISTINCT, "=", $1, $5, @2);
@@ -14661,11 +14657,7 @@ target_el:	a_expr AS ColLabel
 				}
 			/*
 			 * We support omitting AS only for column labels that aren't
-			 * any known keyword.  There is an ambiguity against postfix
-			 * operators: is "a ! b" an infix expression, or a postfix
-			 * expression and a column label?  We prefer to resolve this
-			 * as an infix expression, which we accomplish by assigning
-			 * IDENT a precedence higher than POSTFIXOP.
+			 * any known keyword.
 			 */
 			| a_expr IDENT
 				{
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f69976cc8c..9e266cf3b0 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -57,7 +57,7 @@ bool		Transform_null_equals = false;
 #define PREC_GROUP_NOT_LIKE		9	/* NOT LIKE/ILIKE/SIMILAR */
 #define PREC_GROUP_NOT_BETWEEN	10	/* NOT BETWEEN */
 #define PREC_GROUP_NOT_IN		11	/* NOT IN */
-#define PREC_GROUP_POSTFIX_OP	12	/* generic postfix operators */
+#define PREC_GROUP_ANY_ALL		12	/* ANY/ALL */
 #define PREC_GROUP_INFIX_OP		13	/* generic infix operators */
 #define PREC_GROUP_PREFIX_OP	14	/* generic prefix operators */
 
@@ -71,7 +71,7 @@ bool		Transform_null_equals = false;
  * 4. LIKE ILIKE SIMILAR
  * 5. BETWEEN
  * 6. IN
- * 7. generic postfix Op
+ * 7. ANY ALL
  * 8. generic Op, including <= => <>
  * 9. generic prefix Op
  * 10. IS tests (NullTest, BooleanTest, etc)
@@ -1031,7 +1031,7 @@ transformAExprOpAny(ParseState *pstate, A_Expr *a)
 	Node	   *rexpr = a->rexpr;
 
 	if (operator_precedence_warning)
-		emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
+		emit_precedence_warnings(pstate, PREC_GROUP_ANY_ALL,
 								 strVal(llast(a->name)),
 								 lexpr, NULL,
 								 a->location);
@@ -1054,7 +1054,7 @@ transformAExprOpAll(ParseState *pstate, A_Expr *a)
 	Node	   *rexpr = a->rexpr;
 
 	if (operator_precedence_warning)
-		emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
+		emit_precedence_warnings(pstate, PREC_GROUP_ANY_ALL,
 								 strVal(llast(a->name)),
 								 lexpr, NULL,
 								 a->location);
@@ -2014,15 +2014,9 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 
 		if (operator_precedence_warning)
 		{
-			if (sublink->operName == NIL)
-				emit_precedence_warnings(pstate, PREC_GROUP_IN, "IN",
-										 sublink->testexpr, NULL,
-										 sublink->location);
-			else
-				emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
-										 strVal(llast(sublink->operName)),
-										 sublink->testexpr, NULL,
-										 sublink->location);
+			emit_precedence_warnings(pstate, PREC_GROUP_IN, "IN",
+									 sublink->testexpr, NULL,
+									 sublink->location);
 		}
 
 		/*
@@ -3244,28 +3238,11 @@ operator_precedence_group(Node *node, const char **nodename)
 				group = PREC_GROUP_PREFIX_OP;
 			}
 		}
-		else if (aexpr->kind == AEXPR_OP &&
-				 aexpr->lexpr != NULL &&
-				 aexpr->rexpr == NULL)
-		{
-			/* postfix operator */
-			if (list_length(aexpr->name) == 1)
-			{
-				*nodename = strVal(linitial(aexpr->name));
-				group = PREC_GROUP_POSTFIX_OP;
-			}
-			else
-			{
-				/* schema-qualified operator syntax */
-				*nodename = "OPERATOR()";
-				group = PREC_GROUP_POSTFIX_OP;
-			}
-		}
 		else if (aexpr->kind == AEXPR_OP_ANY ||
 				 aexpr->kind == AEXPR_OP_ALL)
 		{
 			*nodename = strVal(llast(aexpr->name));
-			group = PREC_GROUP_POSTFIX_OP;
+			group = PREC_GROUP_ANY_ALL;
 		}
 		else if (aexpr->kind == AEXPR_DISTINCT ||
 				 aexpr->kind == AEXPR_NOT_DISTINCT)
@@ -3356,7 +3333,7 @@ operator_precedence_group(Node *node, const char **nodename)
 			else
 			{
 				*nodename = strVal(llast(s->operName));
-				group = PREC_GROUP_POSTFIX_OP;
+				group = PREC_GROUP_ANY_ALL;
 			}
 		}
 	}
@@ -3432,9 +3409,8 @@ emit_precedence_warnings(ParseState *pstate,
 	 * Complain if left child, which should be same or higher precedence
 	 * according to current rules, used to be lower precedence.
 	 *
-	 * Exception to precedence rules: if left child is IN or NOT IN or a
-	 * postfix operator, the grouping is syntactically forced regardless of
-	 * precedence.
+	 * Exception to precedence rules: if left child is IN or NOT IN the
+	 * grouping is syntactically forced regardless of precedence.
 	 */
 	cgroup = operator_precedence_group(lchild, &copname);
 	if (cgroup > 0)
@@ -3442,7 +3418,7 @@ emit_precedence_warnings(ParseState *pstate,
 		if (oldprecedence_l[cgroup] < oldprecedence_r[opgroup] &&
 			cgroup != PREC_GROUP_IN &&
 			cgroup != PREC_GROUP_NOT_IN &&
-			cgroup != PREC_GROUP_POSTFIX_OP &&
+			cgroup != PREC_GROUP_ANY_ALL &&
 			cgroup != PREC_GROUP_POSTFIX_IS)
 			ereport(WARNING,
 					(errmsg("operator precedence change: %s is now lower precedence than %s",
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 2749974f63..877ffa03a6 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -52,7 +52,7 @@ typedef struct OprCacheKey
 {
 	char		oprname[NAMEDATALEN];
 	Oid			left_arg;		/* Left input OID, or 0 if prefix op */
-	Oid			right_arg;		/* Right input OID, or 0 if postfix op */
+	Oid			right_arg;		/* Right input OID */
 	Oid			search_path[MAX_CACHED_PATH_LEN];
 } OprCacheKey;
 
@@ -88,8 +88,7 @@ static void InvalidateOprCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
  *		Given a possibly-qualified operator name and exact input datatypes,
  *		look up the operator.
  *
- * Pass oprleft = InvalidOid for a prefix op, oprright = InvalidOid for
- * a postfix op.
+ * Pass oprleft = InvalidOid for a prefix op.
  *
  * If the operator name is not schema-qualified, it is sought in the current
  * namespace search path.
@@ -115,10 +114,13 @@ LookupOperName(ParseState *pstate, List *opername, Oid oprleft, Oid oprright,
 
 		if (!OidIsValid(oprleft))
 			oprkind = 'l';
-		else if (!OidIsValid(oprright))
-			oprkind = 'r';
-		else
+		else if (OidIsValid(oprright))
 			oprkind = 'b';
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("postfix operators are not supported"),
+					 parser_errposition(pstate, location)));
 
 		ereport(ERROR,
 				(errcode(ERRCODE_UNDEFINED_FUNCTION),
@@ -507,85 +509,6 @@ compatible_oper_opid(List *op, Oid arg1, Oid arg2, bool noError)
 }
 
 
-/* right_oper() -- search for a unary right operator (postfix operator)
- * Given operator name and type of arg, return oper struct.
- *
- * IMPORTANT: the returned operator (if any) is only promised to be
- * coercion-compatible with the input datatype.  Do not use this if
- * you need an exact- or binary-compatible match.
- *
- * If no matching operator found, return NULL if noError is true,
- * raise an error if it is false.  pstate and location are used only to report
- * the error position; pass NULL/-1 if not available.
- *
- * NOTE: on success, the returned object is a syscache entry.  The caller
- * must ReleaseSysCache() the entry when done with it.
- */
-Operator
-right_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
-{
-	Oid			operOid;
-	OprCacheKey key;
-	bool		key_ok;
-	FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
-	HeapTuple	tup = NULL;
-
-	/*
-	 * Try to find the mapping in the lookaside cache.
-	 */
-	key_ok = make_oper_cache_key(pstate, &key, op, arg, InvalidOid, location);
-
-	if (key_ok)
-	{
-		operOid = find_oper_cache_entry(&key);
-		if (OidIsValid(operOid))
-		{
-			tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
-			if (HeapTupleIsValid(tup))
-				return (Operator) tup;
-		}
-	}
-
-	/*
-	 * First try for an "exact" match.
-	 */
-	operOid = OpernameGetOprid(op, arg, InvalidOid);
-	if (!OidIsValid(operOid))
-	{
-		/*
-		 * Otherwise, search for the most suitable candidate.
-		 */
-		FuncCandidateList clist;
-
-		/* Get postfix operators of given name */
-		clist = OpernameGetCandidates(op, 'r', false);
-
-		/* No operators found? Then fail... */
-		if (clist != NULL)
-		{
-			/*
-			 * We must run oper_select_candidate even if only one candidate,
-			 * otherwise we may falsely return a non-type-compatible operator.
-			 */
-			fdresult = oper_select_candidate(1, &arg, clist, &operOid);
-		}
-	}
-
-	if (OidIsValid(operOid))
-		tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
-
-	if (HeapTupleIsValid(tup))
-	{
-		if (key_ok)
-			make_oper_cache_entry(&key, operOid);
-	}
-	else if (!noError)
-		op_error(pstate, op, 'r', arg, InvalidOid, fdresult, location);
-
-	return (Operator) tup;
-}
-
-
 /* left_oper() -- search for a unary left operator (prefix operator)
  * Given operator name and type of arg, return oper struct.
  *
@@ -696,8 +619,7 @@ op_signature_string(List *op, char oprkind, Oid arg1, Oid arg2)
 
 	appendStringInfoString(&argbuf, NameListToString(op));
 
-	if (oprkind != 'r')
-		appendStringInfo(&argbuf, " %s", format_type_be(arg2));
+	appendStringInfo(&argbuf, " %s", format_type_be(arg2));
 
 	return argbuf.data;			/* return palloc'd string buffer */
 }
@@ -758,15 +680,14 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 	Oid			rettype;
 	OpExpr	   *result;
 
-	/* Select the operator */
+	/* Check it's not a postfix operator */
 	if (rtree == NULL)
-	{
-		/* right operator */
-		ltypeId = exprType(ltree);
-		rtypeId = InvalidOid;
-		tup = right_oper(pstate, opname, ltypeId, false, location);
-	}
-	else if (ltree == NULL)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("postfix operators are not supported")));
+
+	/* Select the operator */
+	if (ltree == NULL)
 	{
 		/* left operator */
 		rtypeId = exprType(rtree);
@@ -795,15 +716,7 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
 				 parser_errposition(pstate, location)));
 
 	/* Do typecasting and build the expression tree */
-	if (rtree == NULL)
-	{
-		/* right operator */
-		args = list_make1(ltree);
-		actual_arg_types[0] = ltypeId;
-		declared_arg_types[0] = opform->oprleft;
-		nargs = 1;
-	}
-	else if (ltree == NULL)
+	if (ltree == NULL)
 	{
 		/* left operator */
 		args = list_make1(rtree);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 60dd80c23c..16abfcb406 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9207,25 +9207,16 @@ get_oper_expr(OpExpr *expr, deparse_context *context)
 		if (!HeapTupleIsValid(tp))
 			elog(ERROR, "cache lookup failed for operator %u", opno);
 		optup = (Form_pg_operator) GETSTRUCT(tp);
-		switch (optup->oprkind)
-		{
-			case 'l':
-				appendStringInfo(buf, "%s ",
-								 generate_operator_name(opno,
-														InvalidOid,
-														exprType(arg)));
-				get_rule_expr_paren(arg, context, true, (Node *) expr);
-				break;
-			case 'r':
-				get_rule_expr_paren(arg, context, true, (Node *) expr);
-				appendStringInfo(buf, " %s",
-								 generate_operator_name(opno,
-														exprType(arg),
-														InvalidOid));
-				break;
-			default:
-				elog(ERROR, "bogus oprkind: %d", optup->oprkind);
+		if (optup->oprkind == 'l')
+		{
+			appendStringInfo(buf, "%s ",
+							 generate_operator_name(opno,
+													InvalidOid,
+													exprType(arg)));
+			get_rule_expr_paren(arg, context, true, (Node *) expr);
 		}
+		else
+			elog(ERROR, "bogus oprkind: %d", optup->oprkind);
 		ReleaseSysCache(tp);
 	}
 	if (!PRETTY_PAREN(context))
@@ -11087,10 +11078,6 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
 			p_result = left_oper(NULL, list_make1(makeString(oprname)), arg2,
 								 true, -1);
 			break;
-		case 'r':
-			p_result = right_oper(NULL, list_make1(makeString(oprname)), arg1,
-								  true, -1);
-			break;
 		default:
 			elog(ERROR, "unrecognized oprkind: %d", operform->oprkind);
 			p_result = NULL;	/* keep compiler quiet */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2cb3f9b083..105327b3d8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12632,6 +12632,11 @@ dumpOpr(Archive *fout, OprInfo *oprinfo)
 	oprcanmerge = PQgetvalue(res, 0, i_oprcanmerge);
 	oprcanhash = PQgetvalue(res, 0, i_oprcanhash);
 
+	/* In PG14 upwards right unary operator support does not exist anymore. */
+	if (strcmp(oprkind, "r") == 0)
+		pg_log_warning("right unary operators are not supported anymore (operator \"%s\")",
+					   oprcode);
+
 	oprregproc = convertRegProcReference(fout, oprcode);
 	if (oprregproc)
 	{
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 654e2ec514..c7f06604c6 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -203,7 +203,7 @@ DOTypeNameCompare(const void *p1, const void *p2)
 		OprInfo    *oobj1 = *(OprInfo *const *) p1;
 		OprInfo    *oobj2 = *(OprInfo *const *) p2;
 
-		/* oprkind is 'l', 'r', or 'b'; this sorts prefix, postfix, infix */
+		/* oprkind is 'l' or 'b'; this sorts prefix and infix */
 		cmpval = (oobj2->oprkind - oobj1->oprkind);
 		if (cmpval != 0)
 			return cmpval;
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 00aef855dc..b86098dc42 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -22,6 +22,7 @@ static void check_is_install_user(ClusterInfo *cluster);
 static void check_proper_datallowconn(ClusterInfo *cluster);
 static void check_for_prepared_transactions(ClusterInfo *cluster);
 static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster);
+static void check_for_user_defined_postfix_ops(ClusterInfo *cluster);
 static void check_for_tables_with_oids(ClusterInfo *cluster);
 static void check_for_reg_data_type_usage(ClusterInfo *cluster);
 static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
@@ -100,6 +101,13 @@ check_and_dump_old_cluster(bool live_check)
 	check_for_reg_data_type_usage(&old_cluster);
 	check_for_isn_and_int8_passing_mismatch(&old_cluster);
 
+	/*
+	 * Pre-PG 14 allowed user defined postfix operators, which are not
+	 * supported anymore.  Verify there are none, iff applicable.
+	 */
+	if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1300)
+		check_for_user_defined_postfix_ops(&old_cluster);
+
 	/*
 	 * Pre-PG 12 allowed tables to be declared WITH OIDS, which is not
 	 * supported anymore. Verify there are none, iff applicable.
@@ -896,6 +904,104 @@ check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster)
 		check_ok();
 }
 
+/*
+ * Verify that no user defined postfix operators exist.
+ */
+static void
+check_for_user_defined_postfix_ops(ClusterInfo *cluster)
+{
+	int			dbnum;
+	FILE	   *script = NULL;
+	bool		found = false;
+	char		output_path[MAXPGPATH];
+
+	prep_status("Checking for user defined postfix operators");
+
+	snprintf(output_path, sizeof(output_path),
+			 "postfix_ops.txt");
+
+	/* Find any user defined postfix operators */
+	for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
+	{
+		PGresult   *res;
+		bool		db_used = false;
+		int			ntups;
+		int			rowno;
+		int			i_oproid,
+					i_oprnsp,
+					i_oprname,
+					i_typnsp,
+					i_typname;
+		DbInfo	   *active_db = &cluster->dbarr.dbs[dbnum];
+		PGconn	   *conn = connectToServer(cluster, active_db->db_name);
+
+		/*
+		 * The query below hardcodes FirstNormalObjectId as 16384 rather than
+		 * interpolating that C #define into the string because, if that
+		 * #define is ever changed, the cutoff we want to use is the definition
+		 * from pre-verion 14 servers, not from some future version of the
+		 * code.
+		 */
+		res = executeQueryOrDie(conn,
+								"SELECT o.oid AS oproid, "
+								"       n.nspname AS oprnsp, "
+								"       o.oprname, "
+								"       tn.nspname AS typnsp, "
+								"       t.typname "
+								"FROM pg_catalog.pg_operator o, "
+								"     pg_catalog.pg_namespace n, "
+								"     pg_catalog.pg_type t, "
+								"     pg_catalog.pg_namespace tn "
+								"WHERE o.oprnamespace = n.oid AND "
+								"      o.oprleft = t.oid AND "
+								"      t.typnamespace = tn.oid AND "
+								"      o.oprright = 0 AND "
+								"      o.oid >= 16384");
+		ntups = PQntuples(res);
+		i_oproid = PQfnumber(res, "oproid");
+		i_oprnsp = PQfnumber(res, "oprnsp");
+		i_oprname = PQfnumber(res, "oprname");
+		i_typnsp = PQfnumber(res, "typnsp");
+		i_typname = PQfnumber(res, "typname");
+		for (rowno = 0; rowno < ntups; rowno++)
+		{
+			found = true;
+			if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
+				pg_fatal("could not open file \"%s\": %s\n",
+						 output_path, strerror(errno));
+			if (!db_used)
+			{
+				fprintf(script, "In database: %s\n", active_db->db_name);
+				db_used = true;
+			}
+			fprintf(script, "  (oid=%s) %s.%s (%s.%s)\n",
+					PQgetvalue(res, rowno, i_oproid),
+					PQgetvalue(res, rowno, i_oprnsp),
+					PQgetvalue(res, rowno, i_oprname),
+					PQgetvalue(res, rowno, i_typnsp),
+					PQgetvalue(res, rowno, i_typname));
+		}
+
+		PQclear(res);
+
+		PQfinish(conn);
+	}
+
+	if (script)
+		fclose(script);
+
+	if (found)
+	{
+		pg_log(PG_REPORT, "fatal\n");
+		pg_fatal("Your installation contains user defined postfix operators, which is not\n"
+				 "supported anymore.  Consider dropping the postfix operators and replacing\n"
+				 "them with prefix operators or function calls.\n"
+				 "A list of user defined postfix operators is in the file:\n"
+				 "    %s\n\n", output_path);
+	}
+	else
+		check_ok();
+}
 
 /*
  * Verify that no tables are declared WITH OIDS.
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0266fc5fa8..7deb1b09d2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -805,7 +805,7 @@ describeOperators(const char *pattern, bool verbose, bool showSystem)
 					  "SELECT n.nspname as \"%s\",\n"
 					  "  o.oprname AS \"%s\",\n"
 					  "  CASE WHEN o.oprkind='l' THEN NULL ELSE pg_catalog.format_type(o.oprleft, NULL) END AS \"%s\",\n"
-					  "  CASE WHEN o.oprkind='r' THEN NULL ELSE pg_catalog.format_type(o.oprright, NULL) END AS \"%s\",\n"
+					  "  pg_catalog.format_type(o.oprright, NULL) AS \"%s\",\n"
 					  "  pg_catalog.format_type(o.oprresult, NULL) AS \"%s\",\n",
 					  gettext_noop("Schema"),
 					  gettext_noop("Name"),
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 4f8b9865ef..7cc812adda 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -218,12 +218,6 @@
   oprname => '>=', oprleft => 'xid8', oprright => 'xid8', oprresult => 'bool',
   oprcom => '<=(xid8,xid8)', oprnegate => '<(xid8,xid8)', oprcode => 'xid8ge',
   oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' },
-{ oid => '388', descr => 'deprecated, use factorial() instead',
-  oprname => '!', oprkind => 'r', oprleft => 'int8', oprright => '0',
-  oprresult => 'numeric', oprcode => 'numeric_fac' },
-{ oid => '389', descr => 'deprecated, use factorial() instead',
-  oprname => '!!', oprkind => 'l', oprleft => '0', oprright => 'int8',
-  oprresult => 'numeric', oprcode => 'numeric_fac' },
 { oid => '385', descr => 'equal',
   oprname => '=', oprcanhash => 't', oprleft => 'cid', oprright => 'cid',
   oprresult => 'bool', oprcom => '=(cid,cid)', oprcode => 'cideq',
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 1daa263852..1491b87582 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -41,7 +41,7 @@ CATALOG(pg_operator,2617,OperatorRelationId)
 	/* operator owner */
 	Oid			oprowner BKI_DEFAULT(PGUID);
 
-	/* 'l', 'r', or 'b' */
+	/* 'l' or 'b' */
 	char		oprkind BKI_DEFAULT(b);
 
 	/* can be used in merge join? */
@@ -53,7 +53,7 @@ CATALOG(pg_operator,2617,OperatorRelationId)
 	/* left arg type, or 0 if 'l' oprkind */
 	Oid			oprleft BKI_LOOKUP(pg_type);
 
-	/* right arg type, or 0 if 'r' oprkind */
+	/* right arg type */
 	Oid			oprright BKI_LOOKUP(pg_type);
 
 	/* result datatype */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1dd325e0e6..f5af410434 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -327,10 +327,6 @@
 { oid => '110', descr => 'I/O',
   proname => 'unknownout', prorettype => 'cstring', proargtypes => 'unknown',
   prosrc => 'unknownout' },
-{ oid => '111',
-  descr => 'implementation of deprecated ! and !! factorial operators',
-  proname => 'numeric_fac', prorettype => 'numeric', proargtypes => 'int8',
-  prosrc => 'numeric_fac' },
 
 { oid => '115',
   proname => 'box_above_eq', prorettype => 'bool', proargtypes => 'box box',
diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out
index 54e8b79159..9d97564a4c 100644
--- a/src/test/regress/expected/create_operator.out
+++ b/src/test/regress/expected/create_operator.out
@@ -15,16 +15,16 @@ CREATE OPERATOR <% (
    negator = >=%
 );
 CREATE OPERATOR @#@ (
-   rightarg = int8,		-- left unary
-   procedure = numeric_fac
+   rightarg = float8,	-- left unary
+   procedure = dsqrt
 );
 CREATE OPERATOR #@# (
-   leftarg = int8,		-- right unary
-   procedure = numeric_fac
+   rightarg = float8,	-- left unary
+   procedure = dsqrt
 );
 CREATE OPERATOR #%# (
-   leftarg = int8,		-- right unary
-   procedure = numeric_fac
+   rightarg = float8,	-- left unary
+   procedure = dsqrt
 );
 -- Test operator created above
 SELECT point '(1,2)' <% widget '(0,0,3)' AS t,
@@ -36,7 +36,10 @@ SELECT point '(1,2)' <% widget '(0,0,3)' AS t,
 
 -- Test comments
 COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary';
-ERROR:  operator does not exist: integer ######
+ERROR:  operator right argument must be specified
+HINT:  Postfix operators are not supported.
+COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad left unary';
+ERROR:  operator does not exist: ###### integer
 -- => is disallowed now
 CREATE OPERATOR => (
    leftarg = int8,		-- right unary
@@ -52,12 +55,12 @@ CREATE OPERATOR !=- (
    leftarg = int8,		-- right unary
    procedure = numeric_fac
 );
+ERROR:  operator right argument must be specified
+HINT:  Postfix operators are not supported.
 SELECT 2 !=-;
- ?column? 
-----------
-        2
-(1 row)
-
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT 2 !=-;
+                    ^
 -- make sure lexer returns != as <> even in edge cases
 SELECT 2 !=/**/ 1, 2 !=/**/ 2;
  ?column? | ?column? 
@@ -172,11 +175,13 @@ CREATE OPERATOR #@%# (
    invalid_att = int8
 );
 WARNING:  operator attribute "invalid_att" not recognized
--- Should fail. At least leftarg or rightarg should be mandatorily specified
+ERROR:  operator right argument must be specified
+HINT:  Postfix operators are not supported.
+-- Should fail. At least rightarg should be mandatorily specified
 CREATE OPERATOR #@%# (
    procedure = numeric_fac
 );
-ERROR:  at least one of leftarg or rightarg must be specified
+ERROR:  operator arguments must be specified
 -- Should fail. Procedure should be mandatorily specified
 CREATE OPERATOR #@%# (
    leftarg = int8
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 8546ce901f..14617c1117 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2960,37 +2960,34 @@ ERROR:  value overflows numeric format
 --
 -- Tests for factorial
 --
-SELECT 4!;
- ?column? 
-----------
-       24
-(1 row)
-
-SELECT !!3;
- ?column? 
-----------
-        6
-(1 row)
-
 SELECT factorial(15);
    factorial   
 ---------------
  1307674368000
 (1 row)
 
-SELECT 100000!;
+SELECT factorial(100000);
 ERROR:  value overflows numeric format
-SELECT 0!;
- ?column? 
-----------
-        1
+SELECT factorial(0);
+ factorial 
+-----------
+         1
 (1 row)
 
-SELECT -4!;
-ERROR:  factorial of a negative number is undefined
 SELECT factorial(-4);
 ERROR:  factorial of a negative number is undefined
 --
+-- Postfix operators are not supported.  Check that.
+--
+SELECT -5!;
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT -5!;
+                  ^
+SELECT 5!;
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT 5!;
+                 ^
+--
 -- Tests for pg_lsn()
 --
 SELECT pg_lsn(23783416::numeric);
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 1b3c146e4c..7825a765cd 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1066,7 +1066,7 @@ WHERE condefault AND
 -- Look for illegal values in pg_operator fields.
 SELECT p1.oid, p1.oprname
 FROM pg_operator as p1
-WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l' AND p1.oprkind != 'r') OR
+WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l') OR
     p1.oprresult = 0 OR p1.oprcode = 0;
  oid | oprname 
 -----+---------
@@ -1077,8 +1077,7 @@ SELECT p1.oid, p1.oprname
 FROM pg_operator as p1
 WHERE (p1.oprleft = 0 and p1.oprkind != 'l') OR
     (p1.oprleft != 0 and p1.oprkind = 'l') OR
-    (p1.oprright = 0 and p1.oprkind != 'r') OR
-    (p1.oprright != 0 and p1.oprkind = 'r');
+    p1.oprright = 0;
  oid | oprname 
 -----+---------
 (0 rows)
@@ -1285,18 +1284,6 @@ WHERE p1.oprcode = p2.oid AND
 -----+---------+-----+---------
 (0 rows)
 
-SELECT p1.oid, p1.oprname, p2.oid, p2.proname
-FROM pg_operator AS p1, pg_proc AS p2
-WHERE p1.oprcode = p2.oid AND
-    p1.oprkind = 'r' AND
-    (p2.pronargs != 1
-     OR NOT binary_coercible(p2.prorettype, p1.oprresult)
-     OR NOT binary_coercible(p1.oprleft, p2.proargtypes[0])
-     OR p1.oprright != 0);
- oid | oprname | oid | proname 
------+---------+-----+---------
-(0 rows)
-
 -- If the operator is mergejoinable or hashjoinable, its underlying function
 -- should not be volatile.
 SELECT p1.oid, p1.oprname, p2.oid, p2.proname
diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql
index 8b6fd0bb43..d6c3dc98c1 100644
--- a/src/test/regress/sql/create_operator.sql
+++ b/src/test/regress/sql/create_operator.sql
@@ -18,18 +18,18 @@ CREATE OPERATOR <% (
 );
 
 CREATE OPERATOR @#@ (
-   rightarg = int8,		-- left unary
-   procedure = numeric_fac
+   rightarg = float8,	-- left unary
+   procedure = dsqrt
 );
 
 CREATE OPERATOR #@# (
-   leftarg = int8,		-- right unary
-   procedure = numeric_fac
+   rightarg = float8,	-- left unary
+   procedure = dsqrt
 );
 
 CREATE OPERATOR #%# (
-   leftarg = int8,		-- right unary
-   procedure = numeric_fac
+   rightarg = float8,	-- left unary
+   procedure = dsqrt
 );
 
 -- Test operator created above
@@ -38,6 +38,7 @@ SELECT point '(1,2)' <% widget '(0,0,3)' AS t,
 
 -- Test comments
 COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary';
+COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad left unary';
 
 -- => is disallowed now
 CREATE OPERATOR => (
@@ -133,7 +134,7 @@ CREATE OPERATOR #@%# (
    invalid_att = int8
 );
 
--- Should fail. At least leftarg or rightarg should be mandatorily specified
+-- Should fail. At least rightarg should be mandatorily specified
 CREATE OPERATOR #@%# (
    procedure = numeric_fac
 );
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 416c16722a..702b522664 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1298,14 +1298,17 @@ SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overfl
 --
 -- Tests for factorial
 --
-SELECT 4!;
-SELECT !!3;
 SELECT factorial(15);
-SELECT 100000!;
-SELECT 0!;
-SELECT -4!;
+SELECT factorial(100000);
+SELECT factorial(0);
 SELECT factorial(-4);
 
+--
+-- Postfix operators are not supported.  Check that.
+--
+SELECT -5!;
+SELECT 5!;
+
 --
 -- Tests for pg_lsn()
 --
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index 7a9180b081..307aab1deb 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -571,7 +571,7 @@ WHERE condefault AND
 
 SELECT p1.oid, p1.oprname
 FROM pg_operator as p1
-WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l' AND p1.oprkind != 'r') OR
+WHERE (p1.oprkind != 'b' AND p1.oprkind != 'l') OR
     p1.oprresult = 0 OR p1.oprcode = 0;
 
 -- Look for missing or unwanted operand types
@@ -580,8 +580,7 @@ SELECT p1.oid, p1.oprname
 FROM pg_operator as p1
 WHERE (p1.oprleft = 0 and p1.oprkind != 'l') OR
     (p1.oprleft != 0 and p1.oprkind = 'l') OR
-    (p1.oprright = 0 and p1.oprkind != 'r') OR
-    (p1.oprright != 0 and p1.oprkind = 'r');
+    p1.oprright = 0;
 
 -- Look for conflicting operator definitions (same names and input datatypes).
 
@@ -715,15 +714,6 @@ WHERE p1.oprcode = p2.oid AND
      OR NOT binary_coercible(p1.oprright, p2.proargtypes[0])
      OR p1.oprleft != 0);
 
-SELECT p1.oid, p1.oprname, p2.oid, p2.proname
-FROM pg_operator AS p1, pg_proc AS p2
-WHERE p1.oprcode = p2.oid AND
-    p1.oprkind = 'r' AND
-    (p2.pronargs != 1
-     OR NOT binary_coercible(p2.prorettype, p1.oprresult)
-     OR NOT binary_coercible(p1.oprleft, p2.proargtypes[0])
-     OR p1.oprright != 0);
-
 -- If the operator is mergejoinable or hashjoinable, its underlying function
 -- should not be volatile.
 
diff --git a/src/tutorial/complex.source b/src/tutorial/complex.source
index 0355926701..65db7d5144 100644
--- a/src/tutorial/complex.source
+++ b/src/tutorial/complex.source
@@ -111,7 +111,7 @@ CREATE FUNCTION complex_add(complex, complex)
    LANGUAGE C IMMUTABLE STRICT;
 
 -- we can now define the operator. We show a binary operator here but you
--- can also define unary operators by omitting either of leftarg or rightarg.
+-- can also define a left unary operator by omitting the leftarg.
 CREATE OPERATOR + (
    leftarg = complex,
    rightarg = complex,
diff --git a/src/tutorial/syscat.source b/src/tutorial/syscat.source
index 3a1767f97b..a84ea0d5c9 100644
--- a/src/tutorial/syscat.source
+++ b/src/tutorial/syscat.source
@@ -110,20 +110,6 @@ SELECT n.nspname, o.oprname AS left_unary,
   ORDER BY nspname, operand;
 
 
---
--- lists all right unary operators
---
-SELECT n.nspname, o.oprname AS right_unary,
-       format_type(left_type.oid, null) AS operand,
-       format_type(result.oid, null) AS return_type
-  FROM pg_namespace n, pg_operator o,
-       pg_type left_type, pg_type result
-  WHERE o.oprnamespace = n.oid
-    and o.oprkind = 'r'          -- right unary
-    and o.oprleft = left_type.oid
-    and o.oprresult = result.oid
-  ORDER BY nspname, operand;
-
 --
 -- lists all binary operators
 --
-- 
2.21.1 (Apple Git-122.3)

