Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Started by Peter Geogheganover 6 years ago33 messages

Anastasia's nbtree deduplication patch [1]https://commitfest.postgresql.org/24/2202/ -- Peter Geoghegan has an open problem that I
would like to find a solution for: it currently assumes that there is
no difference between binary equality and opclass equality. That won't
work for opclasses such as btree/numeric, because compressing equal
numeric datums could destroy display scale if equal numeric datums
were naively lumped together (actually, the deduplication patch
doesn't work like that, but it has other subtle problems due to not
having worked out fundamental definitional issues).

We don't need to be able to assume that binary equality is exactly the
same thing as opclass equality at the level of individual tuples. We
only need to be able to assume that the user cannot observe any
differences when they are shown output for two datums that are
opclass-equal for any opclass that supports deduplication (i.e. cases
like the numeric_ops case just won't work, so we shouldn't event try).
I believe that it would be okay if we treated two IndexTuples as
equivalent and therefore targets to store together in the same posting
list when they happen to have distinct binary representations due to
the original datums having different TOAST input state. In short, the
deduplication patch cannot tolerate being unable to store
opclass-equal IndexTuples in the same posting list when the opclass
(or the underlying type being indexed) somehow allows that equality
isn't equivalence -- that's simply unsupportable. The opclass gets one
chance to say whether or not it vetoes the use of deduplication: at
CREATE INDEX time.

Consumers of this new infrastructure probably won't be limited to the
deduplication feature; the same infrastructure will be needed for a
B-Tree prefix compression patch (I'm thinking of a configurable CREATE
INDEX prefix compression feature). GIN never had to solve this problem
because its indexes are always lossy, and cannot support index-only
scans. It seems likely that a scheme like the one I have in mind can
work for the vast majority of Postgres B-Tree indexes in practice, so
I don't think that the user-visible restrictions I'm considering will
make the patch significantly less useful (it's already very useful).
The most notable restriction for users will almost certainly be not
supporting deduplication within indexes that use nondeterministic
collations. They were already paying a performance penalty during
hashing, though.

I would like to:

* Get some buy-in on whether or not the precise distinctions I would
like to make are correct for deduplication in particular, and as
useful as possible for other cases that we may need to add later on.

* Figure out the exact interface through which opclass/opfamily
authors can represent that their notion of equality is compatible with
deduplication/compression. (I think that the use of nondeterministic
collations should disable deduplication without explicit action from
the operator class -- that should just be baked in.)

* Mark most existing btree operator classes as being compatible with
deduplication as part of making the patch committable. As I said, I
believe that their semantics are already compatible with what we need
for deduplication to work sensibly, aside from a handful of specific
exceptions.

In any case, I'm certain that problems like the btree/numeric display
scale problem are simply not worth solving directly. That would add a
huge amount of complexity for very little benefit.

[1]: https://commitfest.postgresql.org/24/2202/ -- Peter Geoghegan
--
Peter Geoghegan

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Geoghegan (#1)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Peter Geoghegan <pg@bowt.ie> writes:

We don't need to be able to assume that binary equality is exactly the
same thing as opclass equality at the level of individual tuples. We
only need to be able to assume that the user cannot observe any
differences when they are shown output for two datums that are
opclass-equal for any opclass that supports deduplication (i.e. cases
like the numeric_ops case just won't work, so we shouldn't event try).

Hmm, so that would exclude the optimization for numeric, float4/float8,
and nondeterministic text collations. Anything else?

I agree that teaching opclasses to say whether this is okay is a
reasonable approach.

Consumers of this new infrastructure probably won't be limited to the
deduplication feature;

Indeed, we run up against this sort of thing all the time in, eg, planner
optimizations. I think some sort of "equality is precise" indicator
would be really useful for a lot of things.

regards, tom lane

In reply to: Tom Lane (#2)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Sun, Aug 25, 2019 at 1:56 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

Hmm, so that would exclude the optimization for numeric, float4/float8,
and nondeterministic text collations. Anything else?

Any pseudo-type whose output function could possibly be dependent on
the output function of another type (in case it happens to be one of
the types that definitely aren't safe). Maybe we could make fine
distinctions about pseudo-type safety in certain contexts, but that
doesn't matter to the deduplication patch.

I agree that teaching opclasses to say whether this is okay is a
reasonable approach.

Great.

Consumers of this new infrastructure probably won't be limited to the
deduplication feature;

Indeed, we run up against this sort of thing all the time in, eg, planner
optimizations. I think some sort of "equality is precise" indicator
would be really useful for a lot of things.

The case that I happened to think of was "collation strength
reduction". In other words, an optimization that has the planner use a
merge equijoin whose joinqual involves two text columns using the "C"
collation, even though the "C" collation isn't otherwise usable.
Perhaps there are far more compelling planner optimization that I
haven't considered, though. This idea probably has problems with
interesting sort orders that aren't actually that interesting.

--
Peter Geoghegan

In reply to: Peter Geoghegan (#3)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Sun, Aug 25, 2019 at 2:18 PM Peter Geoghegan <pg@bowt.ie> wrote:

Indeed, we run up against this sort of thing all the time in, eg, planner
optimizations. I think some sort of "equality is precise" indicator
would be really useful for a lot of things.

The case that I happened to think of was "collation strength
reduction".

I was thinking of stashing an "equality is precise" flag in the
metapage of each nbtree index, since we will only determine this once,
at CREATE INDEX time. That would make it fairly natural for the
planner to ask about the "equality is precise"-ness of the index at
the same point that it calls _bt_getrootheight(): within
get_relation_info().

--
Peter Geoghegan

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Geoghegan (#4)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Peter Geoghegan <pg@bowt.ie> writes:

I was thinking of stashing an "equality is precise" flag in the
metapage of each nbtree index, since we will only determine this once,
at CREATE INDEX time.

Sure.

That would make it fairly natural for the
planner to ask about the "equality is precise"-ness of the index at
the same point that it calls _bt_getrootheight(): within
get_relation_info().

The planner will almost certainly want to ask the opclass directly,
because most of the places where it wants to know this sort of thing
about operator behavior have nothing to do with indexes.

regards, tom lane

In reply to: Tom Lane (#5)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Sun, Aug 25, 2019 at 2:40 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I was thinking of stashing an "equality is precise" flag in the
metapage of each nbtree index, since we will only determine this once,
at CREATE INDEX time.

Sure.

I suppose that we'd add something new to CREATE OPERATOR CLASS to make
this work? My instinct is to avoid adding things that are only
meaningful for a single AM to interfaces like CREATE OPERATOR CLASS,
but the system already has numerous dependencies on B-Tree opclasses
that seem comparable to me.

There is a single case where nbtree stores a type that differs from
the type actually being indexed by the operator class: the "name"
case, where the underlying storage type is actually cstring. I'm not
sure whether or not this needs to be treated as its own kind of
special case. I suppose that we can ignore it completely, because
we're not directly concerned with the physical representation used
within an index. In fact, a major goal for this new infrastructure is
that nbtree gets to fully own the representation (it just needs to
know about the high level or logical requirements).

--
Peter Geoghegan

In reply to: Peter Geoghegan (#6)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Sun, Aug 25, 2019 at 2:55 PM Peter Geoghegan <pg@bowt.ie> wrote:

I suppose that we'd add something new to CREATE OPERATOR CLASS to make
this work? My instinct is to avoid adding things that are only
meaningful for a single AM to interfaces like CREATE OPERATOR CLASS,
but the system already has numerous dependencies on B-Tree opclasses
that seem comparable to me.

Another question is whether or not it would be okay to define
"equality is precise"-ness to be "the system's generic equality
function works perfectly as a drop-in replacement for my own equality
operator's function". The system's generic equality function could be
the recently added datum_image_eq() function -- that looks like it
will do exactly what I have in mind. This would be a new way of using
datum_image_eq(), I think, since it wouldn't be okay for it to give an
answer that differed from the equality operator's function. It looks
like existing datum_image_eq() callers can deal with false negatives
(but not false positives, which are impossible).

This exceeds what is strictly necessary for the deduplication patch,
but it seems like the patch should make comparisons as fast as
possible in the context of deduplicating items (it would be nice if it
could just use datum_image_eq instead of an insertion scankey when
doing many comparisons to deduplicate items). We can imagine a
datatype with undefined garbage bytes that affect the answer that
datum_image_eq() gives, but could be safe targets for deduplication,
so it's not clear if being this aggressive will work. But maybe that
isn't actually possible among types that aren't inherently unsafe for
deduplication. And maybe we could be more aggressive with
optimizations in numerous other contexts by defining "equality is
precise"-ness as strict binary equality after accounting for TOAST
compression.

--
Peter Geoghegan

#8Antonin Houska
ah@cybertec.at
In reply to: Peter Geoghegan (#1)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Peter Geoghegan <pg@bowt.ie> wrote:

Consumers of this new infrastructure probably won't be limited to the
deduplication feature;

It'd also solve an open problem of the aggregate push-down patch [1]https://commitfest.postgresql.org/24/1247/, in
particular see the mention of pg_opclass in [2]/messages/by-id/10529.1547561178@localhost: the partial aggregate
node below the final join must not put multiple opclass-equal values of
which are not byte-wise equal into the same group because some
information needed by WHERE or JOIN/ON condition may be lost this
way. The scale of the numeric type is the most obvious example.

I would like to:

* Get some buy-in on whether or not the precise distinctions I would
like to make are correct for deduplication in particular, and as
useful as possible for other cases that we may need to add later on.

* Figure out the exact interface through which opclass/opfamily
authors can represent that their notion of equality is compatible with
deduplication/compression.

It's not entirely clear to me whether opclass or opfamily should carry
this information. opclass probably makes more sense for index related
problems and the aggregate push-down patch can live with that. I don't
see particular reason to add any flag to opfamily. (Planner uses uses
both pg_opclass and pg_opfamily catalogs.)

I think the fact that the aggregate push-down would benefit from this
enhancement should affect choice of the new catalog attribute name,
i.e. it should be not mention words as concrete as "deduplication" or
"compression".

(I think that the use of nondeterministic collations should disable
deduplication without explicit action from the operator class -- that
should just be baked in.)

(I think the aggregate push-down needs to consider the nondeterministic
collations too, I missed that so far.)

[1]: https://commitfest.postgresql.org/24/1247/

[2]: /messages/by-id/10529.1547561178@localhost

--
Antonin Houska
Web: https://www.cybertec-postgresql.com

#9Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Antonin Houska (#8)
1 attachment(s)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

26.08.2019 14:15, Antonin Houska wrote:

Peter Geoghegan <pg@bowt.ie> wrote:

Consumers of this new infrastructure probably won't be limited to the
deduplication feature;

It'd also solve an open problem of the aggregate push-down patch [1], in
particular see the mention of pg_opclass in [2]: the partial aggregate
node below the final join must not put multiple opclass-equal values of
which are not byte-wise equal into the same group because some
information needed by WHERE or JOIN/ON condition may be lost this
way. The scale of the numeric type is the most obvious example.

I would like to:

* Get some buy-in on whether or not the precise distinctions I would
like to make are correct for deduplication in particular, and as
useful as possible for other cases that we may need to add later on.

* Figure out the exact interface through which opclass/opfamily
authors can represent that their notion of equality is compatible with
deduplication/compression.

It's not entirely clear to me whether opclass or opfamily should carry
this information. opclass probably makes more sense for index related
problems and the aggregate push-down patch can live with that. I don't
see particular reason to add any flag to opfamily. (Planner uses uses
both pg_opclass and pg_opfamily catalogs.)

I think the fact that the aggregate push-down would benefit from this
enhancement should affect choice of the new catalog attribute name,
i.e. it should be not mention words as concrete as "deduplication" or
"compression".

The patch implementing new opclass option is attached.

It adds new attribute pg_opclass.opcisbitwise, which is set to true if
opclass equality is the same as binary equality.
By default it is true. It is set to false for numeric and float4, float8.

Does anyarray opclasses need special treatment?

New syntax for create opclass is  "CREATE OPERATOR CLASS NOT BITWISE ..."

Any ideas on better names?

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v1-Opclass-bitwise-equality.patchtext/x-patch; name=v1-Opclass-bitwise-equality.patchDownload
commit 5916d188be1cfff798845720d6e955327aa8c693
Author: Anastasia <a.lubennikova@postgrespro.ru>
Date:   Mon Sep 30 19:31:40 2019 +0300

    Opclass bitwise equality check

diff --git a/doc/src/sgml/ref/create_opclass.sgml b/doc/src/sgml/ref/create_opclass.sgml
index dd5252f..eb2e086 100644
--- a/doc/src/sgml/ref/create_opclass.sgml
+++ b/doc/src/sgml/ref/create_opclass.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
+CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT | NOT BITWISE ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
   USING <replaceable class="parameter">index_method</replaceable> [ FAMILY <replaceable class="parameter">family_name</replaceable> ] AS
   {  OPERATOR <replaceable class="parameter">strategy_number</replaceable> <replaceable class="parameter">operator_name</replaceable> [ ( <replaceable class="parameter">op_type</replaceable>, <replaceable class="parameter">op_type</replaceable> ) ] [ FOR SEARCH | FOR ORDER BY <replaceable class="parameter">sort_family_name</replaceable> ]
    | FUNCTION <replaceable class="parameter">support_number</replaceable> [ ( <replaceable class="parameter">op_type</replaceable> [ , <replaceable class="parameter">op_type</replaceable> ] ) ] <replaceable class="parameter">function_name</replaceable> ( <replaceable class="parameter">argument_type</replaceable> [, ...] )
@@ -106,6 +106,18 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
     </listitem>
    </varlistentry>
 
+    <varlistentry>
+    <term><literal>NOT BITWISE</literal></term>
+    <listitem>
+     <para>
+      If present, the operator class equality is not the same as equivalence.
+      For example, two numerics can compare equal but have different scales.
+      Most opclasses implement bitwise equal comparison, alternative behaviour
+      must be set explicitly.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">data_type</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 6a1ccde..bb6a0a7 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -654,6 +654,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(typeoid);
 	values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(stmt->isDefault);
 	values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(storageoid);
+	values[Anum_pg_opclass_opcisbitwise - 1] = BoolGetDatum(!stmt->isNotBitwise);
 
 	tup = heap_form_tuple(rel->rd_att, values, nulls);
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb9..c2cf06e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3785,6 +3785,7 @@ _copyCreateOpClassStmt(const CreateOpClassStmt *from)
 	COPY_NODE_FIELD(datatype);
 	COPY_NODE_FIELD(items);
 	COPY_SCALAR_FIELD(isDefault);
+	COPY_SCALAR_FIELD(isNotBitwise);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014..52e8f0b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1607,6 +1607,7 @@ _equalCreateOpClassStmt(const CreateOpClassStmt *a, const CreateOpClassStmt *b)
 	COMPARE_NODE_FIELD(datatype);
 	COMPARE_NODE_FIELD(items);
 	COMPARE_SCALAR_FIELD(isDefault);
+	COMPARE_SCALAR_FIELD(isNotBitwise);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..45a4f8a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -590,6 +590,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
 
+%type <boolean>		opt_not_bitwise
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -616,7 +618,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
 	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
-	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
+	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BITWISE
 	BOOLEAN_P BOTH BY
 
 	CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
@@ -5951,16 +5953,17 @@ opt_if_not_exists: IF_P NOT EXISTS              { $$ = true; }
  *****************************************************************************/
 
 CreateOpClassStmt:
-			CREATE OPERATOR CLASS any_name opt_default FOR TYPE_P Typename
+			CREATE OPERATOR CLASS any_name opt_default opt_not_bitwise FOR TYPE_P Typename
 			USING access_method opt_opfamily AS opclass_item_list
 				{
 					CreateOpClassStmt *n = makeNode(CreateOpClassStmt);
 					n->opclassname = $4;
 					n->isDefault = $5;
-					n->datatype = $8;
-					n->amname = $10;
-					n->opfamilyname = $11;
-					n->items = $13;
+					n->isNotBitwise = $6;
+					n->datatype = $9;
+					n->amname = $11;
+					n->opfamilyname = $12;
+					n->items = $14;
 					$$ = (Node *) n;
 				}
 		;
@@ -6023,6 +6026,10 @@ opt_default:	DEFAULT						{ $$ = true; }
 			| /*EMPTY*/						{ $$ = false; }
 		;
 
+opt_not_bitwise: NOT BITWISE				{ $$ = true; }
+			| /*EMPTY*/						{ $$ = false; }
+		;
+
 opt_opfamily:	FAMILY any_name				{ $$ = $2; }
 			| /*EMPTY*/						{ $$ = NIL; }
 		;
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index c689b8f..84f867a 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201909251
+#define CATALOG_VERSION_NO	201909301
 
 #endif
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 2d57510..51fccae 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -44,14 +44,14 @@
 { opcmethod => 'hash', opcname => 'date_ops', opcfamily => 'hash/date_ops',
   opcintype => 'date' },
 { opcmethod => 'btree', opcname => 'float4_ops', opcfamily => 'btree/float_ops',
-  opcintype => 'float4' },
+  opcintype => 'float4', opcisbitwise => 'f' },
 { opcmethod => 'hash', opcname => 'float4_ops', opcfamily => 'hash/float_ops',
-  opcintype => 'float4' },
+  opcintype => 'float4', opcisbitwise => 'f' },
 { oid => '3123', oid_symbol => 'FLOAT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'float8_ops', opcfamily => 'btree/float_ops',
-  opcintype => 'float8' },
+  opcintype => 'float8', opcisbitwise => 'f' },
 { opcmethod => 'hash', opcname => 'float8_ops', opcfamily => 'hash/float_ops',
-  opcintype => 'float8' },
+  opcintype => 'float8', opcisbitwise => 'f' },
 { opcmethod => 'btree', opcname => 'inet_ops', opcfamily => 'btree/network_ops',
   opcintype => 'inet' },
 { opcmethod => 'hash', opcname => 'inet_ops', opcfamily => 'hash/network_ops',
@@ -100,9 +100,11 @@
   opcintype => 'name' },
 { oid => '3125', oid_symbol => 'NUMERIC_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'numeric_ops',
-  opcfamily => 'btree/numeric_ops', opcintype => 'numeric' },
+  opcfamily => 'btree/numeric_ops', opcintype => 'numeric',
+  opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'numeric_ops',
-  opcfamily => 'hash/numeric_ops', opcintype => 'numeric' },
+  opcfamily => 'hash/numeric_ops', opcintype => 'numeric',
+  opcisbitwise => 'f'},
 { oid => '1981', oid_symbol => 'OID_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'oid_ops', opcfamily => 'btree/oid_ops',
   opcintype => 'oid' },
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 84853c1..374ac4f 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -73,6 +73,9 @@ CATALOG(pg_opclass,2616,OperatorClassRelationId)
 
 	/* type of data in index, or InvalidOid */
 	Oid			opckeytype BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
+
+	/* T if opclass equality also means "bitwise equality" */
+	bool			opcisbitwise BKI_DEFAULT(t);
 } FormData_pg_opclass;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..cd390a2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2577,6 +2577,7 @@ typedef struct CreateOpClassStmt
 	TypeName   *datatype;		/* datatype of indexed column */
 	List	   *items;			/* List of CreateOpClassItem nodes */
 	bool		isDefault;		/* Should be marked as default for type? */
+	bool		isNotBitwise;		/* Is opclass equality bitwise? */
 } CreateOpClassStmt;
 
 #define OPCLASS_ITEM_OPERATOR		1
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace84..d6a8e8f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -58,6 +58,7 @@ PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
 PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD)
 PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD)
+PG_KEYWORD("bitwise", BITWISE, UNRESERVED_KEYWORD)
 PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD)
 PG_KEYWORD("both", BOTH, RESERVED_KEYWORD)
 PG_KEYWORD("by", BY, UNRESERVED_KEYWORD)
#10Antonin Houska
ah@cybertec.at
In reply to: Anastasia Lubennikova (#9)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Anastasia Lubennikova <a.lubennikova@postgrespro.ru> wrote:

The patch implementing new opclass option is attached.

It adds new attribute pg_opclass.opcisbitwise, which is set to true if opclass
equality is the same as binary equality.
By default it is true.

I think the default value should be false and we should only set it to true
for individual opclasses which do meet the bitwise equality requirement. Also
extension authors should explicitly state that their data types are bitwise
equal. Otherwise the existing opclasses, when created via pg_dump ->
pg_restore, can be used by the system incorrectly.

It is set to false for numeric and float4, float8.

Are you sure about these?

--
Antonin Houska
Web: https://www.cybertec-postgresql.com

#11Andrew Gierth
andrew@tao11.riddles.org.uk
In reply to: Antonin Houska (#10)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

"Antonin" == Antonin Houska <ah@cybertec.at> writes:

It is set to false for numeric and float4, float8.

Antonin> Are you sure about these?

numeric values can compare equal but have different display scales (see
hash_numeric).

float4 and float8 both have representations for -0, which compares equal
to 0. (numeric technically has a representation for -0 too, but I
believe the current code carefully avoids ever generating it.)

--
Andrew (irc:RhodiumToad)

#12Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Antonin Houska (#10)
2 attachment(s)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

01.10.2019 8:41, Antonin Houska wrote:

Anastasia Lubennikova <a.lubennikova@postgrespro.ru> wrote:

The patch implementing new opclass option is attached.

It adds new attribute pg_opclass.opcisbitwise, which is set to true if opclass
equality is the same as binary equality.
By default it is true.

I think the default value should be false and we should only set it to true
for individual opclasses which do meet the bitwise equality requirement. Also
extension authors should explicitly state that their data types are bitwise
equal. Otherwise the existing opclasses, when created via pg_dump ->
pg_restore, can be used by the system incorrectly.

Thank you for the feedback.

At first I implemented bitwise as default, because it is more common .
Though, I agree that it's essential to avoid false positives here.
The new version of the patch is attached. I also updated pg_dump.

A few more open questions:
1) How to handle contrib modules that create new opclasses?
Since default is 'not bitwise' it means that various operator classes
created in extensions
such as bloom, btree_gin and others, won't be able to take advantage of
various optimizations
that require the opclass to be BITWISE.

'v2-Opclass-bitwise-equality-0002' patch simply adds BITWISE keyword
where necessary.

2) Whether we should provide ALTER OPERATOR CLASS SET BITWISE syntax?

3) Current patch modifies regression test so that it checks CREATE
OPCLASS BITWISE syntax.
Is there anything else worth testing? As I see it, this patch is just
about infrastructure changes,
and more specific tests will be added by features that will implement
further optimizations.

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v2-Opclass-bitwise-equality-0002.patchtext/x-patch; name=v2-Opclass-bitwise-equality-0002.patchDownload
diff --git a/contrib/bloom/bloom--1.0.sql b/contrib/bloom/bloom--1.0.sql
index 4e7c922..ca54f81 100644
--- a/contrib/bloom/bloom--1.0.sql
+++ b/contrib/bloom/bloom--1.0.sql
@@ -15,11 +15,11 @@ COMMENT ON ACCESS METHOD bloom IS 'bloom index access method';
 -- Opclasses
 
 CREATE OPERATOR CLASS int4_ops
-DEFAULT FOR TYPE int4 USING bloom AS
+DEFAULT BITWISE FOR TYPE int4 USING bloom AS
 	OPERATOR	1	=(int4, int4),
 	FUNCTION	1	hashint4(int4);
 
 CREATE OPERATOR CLASS text_ops
-DEFAULT FOR TYPE text USING bloom AS
+DEFAULT BITWISE FOR TYPE text USING bloom AS
 	OPERATOR	1	=(text, text),
 	FUNCTION	1	hashtext(text);
diff --git a/contrib/btree_gin/btree_gin--1.0--1.1.sql b/contrib/btree_gin/btree_gin--1.0--1.1.sql
index dd81d27..b6f01cb 100644
--- a/contrib/btree_gin/btree_gin--1.0--1.1.sql
+++ b/contrib/btree_gin/btree_gin--1.0--1.1.sql
@@ -20,7 +20,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS macaddr8_ops
-DEFAULT FOR TYPE macaddr8 USING gin
+DEFAULT BITWISE FOR TYPE macaddr8 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gin/btree_gin--1.0.sql b/contrib/btree_gin/btree_gin--1.0.sql
index cf867ef..473d395 100644
--- a/contrib/btree_gin/btree_gin--1.0.sql
+++ b/contrib/btree_gin/btree_gin--1.0.sql
@@ -24,7 +24,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS int2_ops
-DEFAULT FOR TYPE int2 USING gin
+DEFAULT BITWISE FOR TYPE int2 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -54,7 +54,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS int4_ops
-DEFAULT FOR TYPE int4 USING gin
+DEFAULT BITWISE FOR TYPE int4 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -84,7 +84,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS int8_ops
-DEFAULT FOR TYPE int8 USING gin
+DEFAULT BITWISE FOR TYPE int8 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -114,7 +114,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS float4_ops
-DEFAULT FOR TYPE float4 USING gin
+DEFAULT BITWISE FOR TYPE float4 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -174,7 +174,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS money_ops
-DEFAULT FOR TYPE money USING gin
+DEFAULT BITWISE FOR TYPE money USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -204,7 +204,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS oid_ops
-DEFAULT FOR TYPE oid USING gin
+DEFAULT BITWISE FOR TYPE oid USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -234,7 +234,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS timestamp_ops
-DEFAULT FOR TYPE timestamp USING gin
+DEFAULT BITWISE FOR TYPE timestamp USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -264,7 +264,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gin
+DEFAULT BITWISE FOR TYPE timestamptz USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -294,7 +294,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS time_ops
-DEFAULT FOR TYPE time USING gin
+DEFAULT BITWISE FOR TYPE time USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -324,7 +324,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS timetz_ops
-DEFAULT FOR TYPE timetz USING gin
+DEFAULT BITWISE FOR TYPE timetz USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -354,7 +354,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS date_ops
-DEFAULT FOR TYPE date USING gin
+DEFAULT BITWISE FOR TYPE date USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -384,7 +384,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS interval_ops
-DEFAULT FOR TYPE interval USING gin
+DEFAULT BITWISE FOR TYPE interval USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -414,7 +414,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS macaddr_ops
-DEFAULT FOR TYPE macaddr USING gin
+DEFAULT BITWISE FOR TYPE macaddr USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -444,7 +444,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS inet_ops
-DEFAULT FOR TYPE inet USING gin
+DEFAULT BITWISE FOR TYPE inet USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -474,7 +474,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS cidr_ops
-DEFAULT FOR TYPE cidr USING gin
+DEFAULT BITWISE FOR TYPE cidr USING gin
 AS
     OPERATOR        1       <(inet,inet),
     OPERATOR        2       <=(inet,inet),
@@ -504,7 +504,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS text_ops
-DEFAULT FOR TYPE text USING gin
+DEFAULT BITWISE FOR TYPE text USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -519,7 +519,7 @@ AS
 STORAGE         text;
 
 CREATE OPERATOR CLASS varchar_ops
-DEFAULT FOR TYPE varchar USING gin
+DEFAULT BITWISE FOR TYPE varchar USING gin
 AS
     OPERATOR        1       <(text,text),
     OPERATOR        2       <=(text,text),
@@ -549,7 +549,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS char_ops
-DEFAULT FOR TYPE "char" USING gin
+DEFAULT BITWISE FOR TYPE "char" USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -579,7 +579,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bytea_ops
-DEFAULT FOR TYPE bytea USING gin
+DEFAULT BITWISE FOR TYPE bytea USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -609,7 +609,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bit_ops
-DEFAULT FOR TYPE bit USING gin
+DEFAULT BITWISE FOR TYPE bit USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -639,7 +639,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS varbit_ops
-DEFAULT FOR TYPE varbit USING gin
+DEFAULT BITWISE FOR TYPE varbit USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gin/btree_gin--1.1--1.2.sql b/contrib/btree_gin/btree_gin--1.1--1.2.sql
index 2a16837..f8bd1b4 100644
--- a/contrib/btree_gin/btree_gin--1.1--1.2.sql
+++ b/contrib/btree_gin/btree_gin--1.1--1.2.sql
@@ -32,7 +32,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS enum_ops
-DEFAULT FOR TYPE anyenum USING gin
+DEFAULT BITWISE FOR TYPE anyenum USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gin/btree_gin--1.2--1.3.sql b/contrib/btree_gin/btree_gin--1.2--1.3.sql
index db675b7..1c5d538 100644
--- a/contrib/btree_gin/btree_gin--1.2--1.3.sql
+++ b/contrib/btree_gin/btree_gin--1.2--1.3.sql
@@ -20,7 +20,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS uuid_ops
-DEFAULT FOR TYPE uuid USING gin
+DEFAULT BITWISE FOR TYPE uuid USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -51,7 +51,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS name_ops
-DEFAULT FOR TYPE name USING gin
+DEFAULT BITWISE FOR TYPE name USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -82,7 +82,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bool_ops
-DEFAULT FOR TYPE bool USING gin
+DEFAULT BITWISE FOR TYPE bool USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -113,7 +113,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bpchar_ops
-DEFAULT FOR TYPE bpchar USING gin
+DEFAULT BITWISE FOR TYPE bpchar USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gist/btree_gist--1.2--1.3.sql b/contrib/btree_gist/btree_gist--1.2--1.3.sql
index 726561e..552339c 100644
--- a/contrib/btree_gist/btree_gist--1.2--1.3.sql
+++ b/contrib/btree_gist/btree_gist--1.2--1.3.sql
@@ -43,7 +43,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_uuid_ops
-DEFAULT FOR TYPE uuid USING gist
+DEFAULT BITWISE FOR TYPE uuid USING gist
 AS
 	OPERATOR	1	<   ,
 	OPERATOR	2	<=  ,
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
index 1efe753..8a705b0 100644
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ b/contrib/btree_gist/btree_gist--1.2.sql
@@ -296,7 +296,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
+DEFAULT BITWISE FOR TYPE oid USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -373,7 +373,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
+DEFAULT BITWISE FOR TYPE int2 USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -445,7 +445,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
+DEFAULT BITWISE FOR TYPE int4 USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -518,7 +518,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
+DEFAULT BITWISE FOR TYPE int8 USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -749,7 +749,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
+DEFAULT BITWISE FOR TYPE timestamp USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -773,7 +773,7 @@ ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
+DEFAULT BITWISE FOR TYPE timestamptz USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -855,7 +855,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
+DEFAULT BITWISE FOR TYPE time USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -879,7 +879,7 @@ ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
 
 
 CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
+DEFAULT BITWISE FOR TYPE timetz USING gist
 AS
 	OPERATOR	1	<   ,
 	OPERATOR	2	<=  ,
@@ -950,7 +950,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
+DEFAULT BITWISE FOR TYPE date USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1028,7 +1028,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
+DEFAULT BITWISE FOR TYPE interval USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
@@ -1101,7 +1101,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
+DEFAULT BITWISE FOR TYPE money USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
@@ -1169,7 +1169,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
+DEFAULT BITWISE FOR TYPE macaddr USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
@@ -1240,7 +1240,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
+DEFAULT BITWISE FOR TYPE text USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1263,7 +1263,7 @@ ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
 
 ---- Create the operator class
 CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
+DEFAULT BITWISE FOR TYPE bpchar USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1322,7 +1322,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
+DEFAULT BITWISE FOR TYPE bytea USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1443,7 +1443,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
+DEFAULT BITWISE FOR TYPE bit USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1466,7 +1466,7 @@ ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
+DEFAULT BITWISE FOR TYPE varbit USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1527,7 +1527,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
+DEFAULT BITWISE FOR TYPE inet USING gist
 AS
 	OPERATOR	1	<   ,
 	OPERATOR	2	<=  ,
@@ -1549,7 +1549,7 @@ ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
+DEFAULT BITWISE FOR TYPE cidr USING gist
 AS
 	OPERATOR	1	<  (inet, inet)  ,
 	OPERATOR	2	<= (inet, inet)  ,
diff --git a/contrib/btree_gist/btree_gist--1.3--1.4.sql b/contrib/btree_gist/btree_gist--1.3--1.4.sql
index f77f6c8..557701a 100644
--- a/contrib/btree_gist/btree_gist--1.3--1.4.sql
+++ b/contrib/btree_gist/btree_gist--1.3--1.4.sql
@@ -43,7 +43,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_macaddr8_ops
-DEFAULT FOR TYPE macaddr8 USING gist
+DEFAULT BITWISE FOR TYPE macaddr8 USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
diff --git a/contrib/btree_gist/btree_gist--1.4--1.5.sql b/contrib/btree_gist/btree_gist--1.4--1.5.sql
index cf974c2..14ef593 100644
--- a/contrib/btree_gist/btree_gist--1.4--1.5.sql
+++ b/contrib/btree_gist/btree_gist--1.4--1.5.sql
@@ -48,7 +48,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_enum_ops
-DEFAULT FOR TYPE anyenum USING gist
+DEFAULT BITWISE FOR TYPE anyenum USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
diff --git a/contrib/citext/citext--1.4--1.5.sql b/contrib/citext/citext--1.4--1.5.sql
index 5ae522b..6a3369e 100644
--- a/contrib/citext/citext--1.4--1.5.sql
+++ b/contrib/citext/citext--1.4--1.5.sql
@@ -79,7 +79,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
 
 CREATE OPERATOR CLASS citext_pattern_ops
-FOR TYPE CITEXT USING btree AS
+BITWISE FOR TYPE CITEXT USING btree AS
     OPERATOR    1   ~<~  (citext, citext),
     OPERATOR    2   ~<=~ (citext, citext),
     OPERATOR    3   =    (citext, citext),
diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b06198..dfed78b 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -208,7 +208,7 @@ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
 --
 
 CREATE OPERATOR CLASS citext_ops
-DEFAULT FOR TYPE CITEXT USING btree AS
+DEFAULT BITWISE FOR TYPE CITEXT USING btree AS
     OPERATOR    1   <  (citext, citext),
     OPERATOR    2   <= (citext, citext),
     OPERATOR    3   =  (citext, citext),
@@ -221,7 +221,7 @@ DEFAULT FOR TYPE CITEXT USING btree AS
 --
 
 CREATE OPERATOR CLASS citext_ops
-DEFAULT FOR TYPE citext USING hash AS
+DEFAULT BITWISE FOR TYPE citext USING hash AS
     OPERATOR    1   =  (citext, citext),
     FUNCTION    1   citext_hash(citext);
 
diff --git a/contrib/cube/cube--1.2.sql b/contrib/cube/cube--1.2.sql
index dea2614..faf5a06 100644
--- a/contrib/cube/cube--1.2.sql
+++ b/contrib/cube/cube--1.2.sql
@@ -347,7 +347,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- Create the operator classes for indexing
 
 CREATE OPERATOR CLASS cube_ops
-    DEFAULT FOR TYPE cube USING btree AS
+    DEFAULT BITWISE FOR TYPE cube USING btree AS
         OPERATOR        1       < ,
         OPERATOR        2       <= ,
         OPERATOR        3       = ,
@@ -356,7 +356,7 @@ CREATE OPERATOR CLASS cube_ops
         FUNCTION        1       cube_cmp(cube, cube);
 
 CREATE OPERATOR CLASS gist_cube_ops
-    DEFAULT FOR TYPE cube USING gist AS
+    DEFAULT BITWISE FOR TYPE cube USING gist AS
 	OPERATOR	3	&& ,
 	OPERATOR	6	= ,
 	OPERATOR	7	@> ,
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14..d34896b 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -423,7 +423,7 @@ CREATE OPERATOR #>=# (
 );
 
 CREATE OPERATOR CLASS btree_hstore_ops
-DEFAULT FOR TYPE hstore USING btree
+DEFAULT BITWISE FOR TYPE hstore USING btree
 AS
 	OPERATOR	1	#<# ,
 	OPERATOR	2	#<=# ,
@@ -440,7 +440,7 @@ AS 'MODULE_PATHNAME','hstore_hash'
 LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
 
 CREATE OPERATOR CLASS hash_hstore_ops
-DEFAULT FOR TYPE hstore USING hash
+DEFAULT BITWISE FOR TYPE hstore USING hash
 AS
 	OPERATOR	1	= ,
 	FUNCTION	1	hstore_hash(hstore);
@@ -501,7 +501,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gist_hstore_ops
-DEFAULT FOR TYPE hstore USING gist
+DEFAULT BITWISE FOR TYPE hstore USING gist
 AS
 	OPERATOR        7       @> ,
 	OPERATOR        9       ?(hstore,text) ,
@@ -537,7 +537,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gin_hstore_ops
-DEFAULT FOR TYPE hstore USING gin
+DEFAULT BITWISE FOR TYPE hstore USING gin
 AS
 	OPERATOR        7       @>,
 	OPERATOR        9       ?(hstore,text),
diff --git a/contrib/intarray/intarray--1.2.sql b/contrib/intarray/intarray--1.2.sql
index f10b53d..4cca844 100644
--- a/contrib/intarray/intarray--1.2.sql
+++ b/contrib/intarray/intarray--1.2.sql
@@ -397,7 +397,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- Create the operator class for indexing
 
 CREATE OPERATOR CLASS gist__int_ops
-DEFAULT FOR TYPE _int4 USING gist AS
+DEFAULT BITWISE FOR TYPE _int4 USING gist AS
 	OPERATOR	3	&&,
 	OPERATOR	6	= (anyarray, anyarray),
 	OPERATOR	7	@>,
@@ -473,7 +473,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- register the opclass for indexing (not as default)
 
 CREATE OPERATOR CLASS gist__intbig_ops
-FOR TYPE _int4 USING gist
+BITWISE FOR TYPE _int4 USING gist
 AS
 	OPERATOR	3	&&,
 	OPERATOR	6	= (anyarray, anyarray),
@@ -504,7 +504,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gin__int_ops
-FOR TYPE _int4 USING gin
+BITWISE FOR TYPE _int4 USING gin
 AS
 	OPERATOR	3	&&,
 	OPERATOR	6	= (anyarray, anyarray),
diff --git a/contrib/isn/isn--1.1.sql b/contrib/isn/isn--1.1.sql
index 5206961..f6514e3 100644
--- a/contrib/isn/isn--1.1.sql
+++ b/contrib/isn/isn--1.1.sql
@@ -2716,7 +2716,7 @@ CREATE FUNCTION btean13cmp(ean13, ean13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ean13_ops DEFAULT
+CREATE OPERATOR CLASS ean13_ops DEFAULT BITWISE
 	FOR TYPE ean13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2731,7 +2731,7 @@ CREATE FUNCTION hashean13(ean13)
 	LANGUAGE 'internal' IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ean13_ops DEFAULT
+CREATE OPERATOR CLASS ean13_ops DEFAULT BITWISE
 	FOR TYPE ean13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashean13(ean13);
@@ -2842,7 +2842,7 @@ CREATE FUNCTION btisbn13cmp(isbn13, isbn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn13_ops DEFAULT
+CREATE OPERATOR CLASS isbn13_ops DEFAULT BITWISE
 	FOR TYPE isbn13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2858,7 +2858,7 @@ CREATE FUNCTION hashisbn13(isbn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn13_ops DEFAULT
+CREATE OPERATOR CLASS isbn13_ops DEFAULT BITWISE
 	FOR TYPE isbn13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashisbn13(isbn13);
@@ -2904,7 +2904,7 @@ CREATE FUNCTION btisbncmp(isbn, isbn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn_ops DEFAULT
+CREATE OPERATOR CLASS isbn_ops DEFAULT BITWISE
 	FOR TYPE isbn USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2920,7 +2920,7 @@ CREATE FUNCTION hashisbn(isbn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn_ops DEFAULT
+CREATE OPERATOR CLASS isbn_ops DEFAULT BITWISE
 	FOR TYPE isbn USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashisbn(isbn);
@@ -2966,7 +2966,7 @@ CREATE FUNCTION btismn13cmp(ismn13, ismn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn13_ops DEFAULT
+CREATE OPERATOR CLASS ismn13_ops DEFAULT BITWISE
 	FOR TYPE ismn13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2982,7 +2982,7 @@ CREATE FUNCTION hashismn13(ismn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn13_ops DEFAULT
+CREATE OPERATOR CLASS ismn13_ops DEFAULT BITWISE
 	FOR TYPE ismn13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashismn13(ismn13);
@@ -3028,7 +3028,7 @@ CREATE FUNCTION btismncmp(ismn, ismn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn_ops DEFAULT
+CREATE OPERATOR CLASS ismn_ops DEFAULT BITWISE
 	FOR TYPE ismn USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3044,7 +3044,7 @@ CREATE FUNCTION hashismn(ismn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn_ops DEFAULT
+CREATE OPERATOR CLASS ismn_ops DEFAULT BITWISE
 	FOR TYPE ismn USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashismn(ismn);
@@ -3090,7 +3090,7 @@ CREATE FUNCTION btissn13cmp(issn13, issn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn13_ops DEFAULT
+CREATE OPERATOR CLASS issn13_ops DEFAULT BITWISE
 	FOR TYPE issn13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3106,7 +3106,7 @@ CREATE FUNCTION hashissn13(issn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn13_ops DEFAULT
+CREATE OPERATOR CLASS issn13_ops DEFAULT BITWISE
 	FOR TYPE issn13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashissn13(issn13);
@@ -3152,7 +3152,7 @@ CREATE FUNCTION btissncmp(issn, issn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn_ops DEFAULT
+CREATE OPERATOR CLASS issn_ops DEFAULT BITWISE
 	FOR TYPE issn USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3168,7 +3168,7 @@ CREATE FUNCTION hashissn(issn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn_ops DEFAULT
+CREATE OPERATOR CLASS issn_ops DEFAULT BITWISE
 	FOR TYPE issn USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashissn(issn);
@@ -3214,7 +3214,7 @@ CREATE FUNCTION btupccmp(upc, upc)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS upc_ops DEFAULT
+CREATE OPERATOR CLASS upc_ops DEFAULT BITWISE
 	FOR TYPE upc USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3230,7 +3230,7 @@ CREATE FUNCTION hashupc(upc)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS upc_ops DEFAULT
+CREATE OPERATOR CLASS upc_ops DEFAULT BITWISE
 	FOR TYPE upc USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashupc(upc);
diff --git a/contrib/ltree/ltree--1.1.sql b/contrib/ltree/ltree--1.1.sql
index d46f5fc..3198096 100644
--- a/contrib/ltree/ltree--1.1.sql
+++ b/contrib/ltree/ltree--1.1.sql
@@ -290,7 +290,7 @@ CREATE OPERATOR || (
 -- B-tree support
 
 CREATE OPERATOR CLASS ltree_ops
-    DEFAULT FOR TYPE ltree USING btree AS
+    DEFAULT BITWISE FOR TYPE ltree USING btree AS
         OPERATOR        1       < ,
         OPERATOR        2       <= ,
         OPERATOR        3       = ,
@@ -518,7 +518,7 @@ CREATE FUNCTION ltree_same(ltree_gist, ltree_gist, internal)
 RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gist_ltree_ops
-    DEFAULT FOR TYPE ltree USING gist AS
+    DEFAULT BITWISE FOR TYPE ltree USING gist AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
 	OPERATOR	3	= ,
@@ -853,7 +853,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gist__ltree_ops
-    DEFAULT FOR TYPE _ltree USING gist AS
+    DEFAULT BITWISE FOR TYPE _ltree USING gist AS
 	OPERATOR	10	<@ (_ltree, ltree),
 	OPERATOR	11	@> (ltree, _ltree),
 	OPERATOR	12	~ (_ltree, lquery),
diff --git a/contrib/pg_trgm/pg_trgm--1.3.sql b/contrib/pg_trgm/pg_trgm--1.3.sql
index 5e08e87..ab917d2 100644
--- a/contrib/pg_trgm/pg_trgm--1.3.sql
+++ b/contrib/pg_trgm/pg_trgm--1.3.sql
@@ -168,7 +168,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 -- create the operator class for gist
 CREATE OPERATOR CLASS gist_trgm_ops
-FOR TYPE text USING gist
+BITWISE FOR TYPE text USING gist
 AS
         OPERATOR        1       % (text, text),
         FUNCTION        1       gtrgm_consistent (internal, text, smallint, oid, internal),
@@ -221,7 +221,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 -- create the operator class for gin
 CREATE OPERATOR CLASS gin_trgm_ops
-FOR TYPE text USING gin
+BITWISE FOR TYPE text USING gin
 AS
         OPERATOR        1       % (text, text),
         FUNCTION        1       btint4cmp (int4, int4),
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f0c842a..18bc5b9 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3111,7 +3111,7 @@ create operator public.>^ (
 create operator family my_op_family using btree;
 create function my_op_cmp(a int, b int) returns int as
   $$begin return btint4cmp(a, b); end $$ language plpgsql;
-create operator class my_op_class for type int using btree family my_op_family as
+create operator class my_op_class bitwise for type int using btree family my_op_family as
  operator 1 public.<^,
  operator 3 public.=^,
  operator 5 public.>^,
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 630b803..4438663 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -825,7 +825,7 @@ create operator family my_op_family using btree;
 create function my_op_cmp(a int, b int) returns int as
   $$begin return btint4cmp(a, b); end $$ language plpgsql;
 
-create operator class my_op_class for type int using btree family my_op_family as
+create operator class my_op_class bitwise for type int using btree family my_op_family as
  operator 1 public.<^,
  operator 3 public.=^,
  operator 5 public.>^,
diff --git a/contrib/seg/seg--1.1.sql b/contrib/seg/seg--1.1.sql
index d95aabc..b869071 100644
--- a/contrib/seg/seg--1.1.sql
+++ b/contrib/seg/seg--1.1.sql
@@ -365,7 +365,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- Create the operator classes for indexing
 
 CREATE OPERATOR CLASS seg_ops
-    DEFAULT FOR TYPE seg USING btree AS
+    DEFAULT BITWISE FOR TYPE seg USING btree AS
         OPERATOR        1       < ,
         OPERATOR        2       <= ,
         OPERATOR        3       = ,
@@ -374,7 +374,7 @@ CREATE OPERATOR CLASS seg_ops
         FUNCTION        1       seg_cmp(seg, seg);
 
 CREATE OPERATOR CLASS gist_seg_ops
-DEFAULT FOR TYPE seg USING gist
+DEFAULT BITWISE FOR TYPE seg USING gist
 AS
 	OPERATOR	1	<< ,
 	OPERATOR	2	&< ,
v2-Opclass-bitwise-equality-0001.patchtext/x-patch; name=v2-Opclass-bitwise-equality-0001.patchDownload
diff --git a/doc/src/sgml/ref/create_opclass.sgml b/doc/src/sgml/ref/create_opclass.sgml
index dd5252f..8d0ddfc 100644
--- a/doc/src/sgml/ref/create_opclass.sgml
+++ b/doc/src/sgml/ref/create_opclass.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
+CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT | BITWISE ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
   USING <replaceable class="parameter">index_method</replaceable> [ FAMILY <replaceable class="parameter">family_name</replaceable> ] AS
   {  OPERATOR <replaceable class="parameter">strategy_number</replaceable> <replaceable class="parameter">operator_name</replaceable> [ ( <replaceable class="parameter">op_type</replaceable>, <replaceable class="parameter">op_type</replaceable> ) ] [ FOR SEARCH | FOR ORDER BY <replaceable class="parameter">sort_family_name</replaceable> ]
    | FUNCTION <replaceable class="parameter">support_number</replaceable> [ ( <replaceable class="parameter">op_type</replaceable> [ , <replaceable class="parameter">op_type</replaceable> ] ) ] <replaceable class="parameter">function_name</replaceable> ( <replaceable class="parameter">argument_type</replaceable> [, ...] )
@@ -106,6 +106,20 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
     </listitem>
    </varlistentry>
 
+    <varlistentry>
+    <term><literal>BITWISE</literal></term>
+    <listitem>
+     <para>
+      If present, the operator class equality is the same as equivalence.
+      For example, two integers that compare equal are also binary equal,
+      while, numerics can compare equal but have different scales,
+      thus numeric opclass is not <literal>BITWISE</literal>. Even though,
+      most opclasses implement bitwise equal comparison, this behaviour
+      must be set explicitly.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">data_type</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index 6a1ccde..4e7180a 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -654,6 +654,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(typeoid);
 	values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(stmt->isDefault);
 	values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(storageoid);
+	values[Anum_pg_opclass_opcisbitwise - 1] = BoolGetDatum(stmt->isBitwise);
 
 	tup = heap_form_tuple(rel->rd_att, values, nulls);
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb9..7c5dd7c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3785,6 +3785,7 @@ _copyCreateOpClassStmt(const CreateOpClassStmt *from)
 	COPY_NODE_FIELD(datatype);
 	COPY_NODE_FIELD(items);
 	COPY_SCALAR_FIELD(isDefault);
+	COPY_SCALAR_FIELD(isBitwise);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014..d149cfb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1607,6 +1607,7 @@ _equalCreateOpClassStmt(const CreateOpClassStmt *a, const CreateOpClassStmt *b)
 	COMPARE_NODE_FIELD(datatype);
 	COMPARE_NODE_FIELD(items);
 	COMPARE_SCALAR_FIELD(isDefault);
+	COMPARE_SCALAR_FIELD(isBitwise);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..a2dee33 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -445,7 +445,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <boolean> opt_instead
 %type <boolean> opt_unique opt_concurrently opt_verbose opt_full
-%type <boolean> opt_freeze opt_analyze opt_default opt_recheck
+%type <boolean> opt_freeze opt_analyze opt_default opt_recheck opt_bitwise
 %type <defelt>	opt_binary copy_delimiter
 
 %type <boolean> copy_from opt_program
@@ -616,7 +616,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
 	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
-	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
+	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BITWISE
 	BOOLEAN_P BOTH BY
 
 	CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
@@ -5951,16 +5951,17 @@ opt_if_not_exists: IF_P NOT EXISTS              { $$ = true; }
  *****************************************************************************/
 
 CreateOpClassStmt:
-			CREATE OPERATOR CLASS any_name opt_default FOR TYPE_P Typename
+			CREATE OPERATOR CLASS any_name opt_default opt_bitwise FOR TYPE_P Typename
 			USING access_method opt_opfamily AS opclass_item_list
 				{
 					CreateOpClassStmt *n = makeNode(CreateOpClassStmt);
 					n->opclassname = $4;
 					n->isDefault = $5;
-					n->datatype = $8;
-					n->amname = $10;
-					n->opfamilyname = $11;
-					n->items = $13;
+					n->isBitwise = $6;
+					n->datatype = $9;
+					n->amname = $11;
+					n->opfamilyname = $12;
+					n->items = $14;
 					$$ = (Node *) n;
 				}
 		;
@@ -6023,6 +6024,10 @@ opt_default:	DEFAULT						{ $$ = true; }
 			| /*EMPTY*/						{ $$ = false; }
 		;
 
+opt_bitwise:	BITWISE						{ $$ = true; }
+			| /*EMPTY*/						{ $$ = false; }
+		;
+
 opt_opfamily:	FAMILY any_name				{ $$ = $2; }
 			| /*EMPTY*/						{ $$ = NIL; }
 		;
@@ -15066,6 +15071,7 @@ unreserved_keyword:
 			| BACKWARD
 			| BEFORE
 			| BEGIN_P
+			| BITWISE
 			| BY
 			| CACHE
 			| CALL
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..6a13d19 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12833,6 +12833,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	int			i_opcintype;
 	int			i_opckeytype;
 	int			i_opcdefault;
+	int			i_opcisbitwise;
 	int			i_opcfamily;
 	int			i_opcfamilyname;
 	int			i_opcfamilynsp;
@@ -12849,6 +12850,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	char	   *opcintype;
 	char	   *opckeytype;
 	char	   *opcdefault;
+	char	   *opcisbitwise;
 	char	   *opcfamily;
 	char	   *opcfamilyname;
 	char	   *opcfamilynsp;
@@ -12875,11 +12877,25 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	nameusing = createPQExpBuffer();
 
 	/* Get additional fields from the pg_opclass row */
-	if (fout->remoteVersion >= 80300)
+	if (fout->remoteVersion >= 13000)
+	{
+		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
+						  "opckeytype::pg_catalog.regtype, "
+						  "opcdefault, opcisbitwise, opcfamily, "
+						  "opfname AS opcfamilyname, "
+						  "nspname AS opcfamilynsp, "
+						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
+						  "FROM pg_catalog.pg_opclass c "
+						  "LEFT JOIN pg_catalog.pg_opfamily f ON f.oid = opcfamily "
+						  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = opfnamespace "
+						  "WHERE c.oid = '%u'::pg_catalog.oid",
+						  opcinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 80300)
 	{
 		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
 						  "opckeytype::pg_catalog.regtype, "
-						  "opcdefault, opcfamily, "
+						  "opcdefault, 'f' as opcisbitwise, opcfamily, "
 						  "opfname AS opcfamilyname, "
 						  "nspname AS opcfamilynsp, "
 						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
@@ -12893,7 +12909,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	{
 		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
 						  "opckeytype::pg_catalog.regtype, "
-						  "opcdefault, NULL AS opcfamily, "
+						  "opcdefault, 'f' as opcisbitwise, NULL AS opcfamily, "
 						  "NULL AS opcfamilyname, "
 						  "NULL AS opcfamilynsp, "
 						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcamid) AS amname "
@@ -12907,6 +12923,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	i_opcintype = PQfnumber(res, "opcintype");
 	i_opckeytype = PQfnumber(res, "opckeytype");
 	i_opcdefault = PQfnumber(res, "opcdefault");
+	i_opcisbitwise = PQfnumber(res, "opcisbitwise");
 	i_opcfamily = PQfnumber(res, "opcfamily");
 	i_opcfamilyname = PQfnumber(res, "opcfamilyname");
 	i_opcfamilynsp = PQfnumber(res, "opcfamilynsp");
@@ -12916,6 +12933,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	opcintype = pg_strdup(PQgetvalue(res, 0, i_opcintype));
 	opckeytype = PQgetvalue(res, 0, i_opckeytype);
 	opcdefault = PQgetvalue(res, 0, i_opcdefault);
+	opcisbitwise = PQgetvalue(res, 0, i_opcisbitwise);
 	/* opcfamily will still be needed after we PQclear res */
 	opcfamily = pg_strdup(PQgetvalue(res, 0, i_opcfamily));
 	opcfamilyname = PQgetvalue(res, 0, i_opcfamilyname);
@@ -12933,6 +12951,8 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 					  fmtQualifiedDumpable(opcinfo));
 	if (strcmp(opcdefault, "t") == 0)
 		appendPQExpBufferStr(q, "DEFAULT ");
+	if (strcmp(opcisbitwise, "t") == 0)
+		appendPQExpBufferStr(q, "BITWISE ");
 	appendPQExpBuffer(q, "FOR TYPE %s USING %s",
 					  opcintype,
 					  fmtId(amname));
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 1f6de76..7bc8dbd 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201910251
+#define CATALOG_VERSION_NO	201910281
 
 #endif
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 2d57510..045d9b6 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -17,76 +17,76 @@
 # assigned on-the-fly during initdb.
 
 { opcmethod => 'btree', opcname => 'array_ops', opcfamily => 'btree/array_ops',
-  opcintype => 'anyarray' },
+  opcintype => 'anyarray', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'array_ops', opcfamily => 'hash/array_ops',
-  opcintype => 'anyarray' },
+  opcintype => 'anyarray', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bit_ops', opcfamily => 'btree/bit_ops',
-  opcintype => 'bit' },
+  opcintype => 'bit', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bool_ops', opcfamily => 'btree/bool_ops',
-  opcintype => 'bool' },
+  opcintype => 'bool', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bpchar_ops',
-  opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar' },
+  opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bpchar_ops', opcfamily => 'hash/bpchar_ops',
-  opcintype => 'bpchar' },
+  opcintype => 'bpchar', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bytea_ops', opcfamily => 'btree/bytea_ops',
-  opcintype => 'bytea' },
+  opcintype => 'bytea', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'char_ops', opcfamily => 'btree/char_ops',
-  opcintype => 'char' },
+  opcintype => 'char', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'char_ops', opcfamily => 'hash/char_ops',
-  opcintype => 'char' },
+  opcintype => 'char', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'cidr_ops', opcfamily => 'btree/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'cidr_ops', opcfamily => 'hash/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't'},
 { oid => '3122', oid_symbol => 'DATE_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'date_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'date' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'date', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'date_ops', opcfamily => 'hash/date_ops',
-  opcintype => 'date' },
+  opcintype => 'date', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'float4_ops', opcfamily => 'btree/float_ops',
-  opcintype => 'float4' },
+  opcintype => 'float4', opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'float4_ops', opcfamily => 'hash/float_ops',
-  opcintype => 'float4' },
+  opcintype => 'float4', opcisbitwise => 'f'},
 { oid => '3123', oid_symbol => 'FLOAT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'float8_ops', opcfamily => 'btree/float_ops',
-  opcintype => 'float8' },
+  opcintype => 'float8', opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'float8_ops', opcfamily => 'hash/float_ops',
-  opcintype => 'float8' },
+  opcintype => 'float8', opcisbitwise => 'f'},
 { opcmethod => 'btree', opcname => 'inet_ops', opcfamily => 'btree/network_ops',
-  opcintype => 'inet' },
+  opcintype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'inet_ops', opcfamily => 'hash/network_ops',
-  opcintype => 'inet' },
+  opcintype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'inet_ops', opcfamily => 'gist/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'inet_ops',
-  opcfamily => 'spgist/network_ops', opcintype => 'inet' },
+  opcfamily => 'spgist/network_ops', opcintype => 'inet', opcisbitwise => 't'},
 { oid => '1979', oid_symbol => 'INT2_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int2_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int2' },
+  opcintype => 'int2', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'int2_ops', opcfamily => 'hash/integer_ops',
-  opcintype => 'int2' },
+  opcintype => 'int2', opcisbitwise => 't'},
 { oid => '1978', oid_symbol => 'INT4_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int4_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int4' },
+  opcintype => 'int4', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'int4_ops', opcfamily => 'hash/integer_ops',
-  opcintype => 'int4' },
+  opcintype => 'int4', opcisbitwise => 't'},
 { oid => '3124', oid_symbol => 'INT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int8_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int8' },
+  opcintype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'int8_ops', opcfamily => 'hash/integer_ops',
-  opcintype => 'int8' },
+  opcintype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'interval_ops',
-  opcfamily => 'btree/interval_ops', opcintype => 'interval' },
+  opcfamily => 'btree/interval_ops', opcintype => 'interval', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'interval_ops',
-  opcfamily => 'hash/interval_ops', opcintype => 'interval' },
+  opcfamily => 'hash/interval_ops', opcintype => 'interval', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'macaddr_ops',
-  opcfamily => 'btree/macaddr_ops', opcintype => 'macaddr' },
+  opcfamily => 'btree/macaddr_ops', opcintype => 'macaddr', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'macaddr_ops',
-  opcfamily => 'hash/macaddr_ops', opcintype => 'macaddr' },
+  opcfamily => 'hash/macaddr_ops', opcintype => 'macaddr', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'macaddr8_ops',
-  opcfamily => 'btree/macaddr8_ops', opcintype => 'macaddr8' },
+  opcfamily => 'btree/macaddr8_ops', opcintype => 'macaddr8', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'macaddr8_ops',
-  opcfamily => 'hash/macaddr8_ops', opcintype => 'macaddr8' },
+  opcfamily => 'hash/macaddr8_ops', opcintype => 'macaddr8', opcisbitwise => 't'},
 
 # Here's an ugly little hack to save space in the system catalog indexes.
 # btree doesn't ordinarily allow a storage type different from input type;
@@ -94,157 +94,159 @@
 # and we can safely omit that within an index entry.  So we declare the
 # btree opclass for name as using cstring storage type.
 { opcmethod => 'btree', opcname => 'name_ops', opcfamily => 'btree/text_ops',
-  opcintype => 'name', opckeytype => 'cstring' },
+  opcintype => 'name', opckeytype => 'cstring', opcisbitwise => 't'},
 
 { opcmethod => 'hash', opcname => 'name_ops', opcfamily => 'hash/text_ops',
-  opcintype => 'name' },
+  opcintype => 'name', opcisbitwise => 't'},
 { oid => '3125', oid_symbol => 'NUMERIC_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'numeric_ops',
-  opcfamily => 'btree/numeric_ops', opcintype => 'numeric' },
+  opcfamily => 'btree/numeric_ops', opcintype => 'numeric',
+  opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'numeric_ops',
-  opcfamily => 'hash/numeric_ops', opcintype => 'numeric' },
+  opcfamily => 'hash/numeric_ops', opcintype => 'numeric',
+  opcisbitwise => 'f'},
 { oid => '1981', oid_symbol => 'OID_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'oid_ops', opcfamily => 'btree/oid_ops',
-  opcintype => 'oid' },
+  opcintype => 'oid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'oid_ops', opcfamily => 'hash/oid_ops',
-  opcintype => 'oid' },
+  opcintype => 'oid', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'oidvector_ops',
-  opcfamily => 'btree/oidvector_ops', opcintype => 'oidvector' },
+  opcfamily => 'btree/oidvector_ops', opcintype => 'oidvector', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'oidvector_ops',
-  opcfamily => 'hash/oidvector_ops', opcintype => 'oidvector' },
+  opcfamily => 'hash/oidvector_ops', opcintype => 'oidvector', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'record_ops',
-  opcfamily => 'btree/record_ops', opcintype => 'record' },
+  opcfamily => 'btree/record_ops', opcintype => 'record', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'record_image_ops',
   opcfamily => 'btree/record_image_ops', opcintype => 'record',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { oid => '3126', oid_symbol => 'TEXT_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'text_ops', opcfamily => 'btree/text_ops',
-  opcintype => 'text' },
+  opcintype => 'text', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'text_ops', opcfamily => 'hash/text_ops',
-  opcintype => 'text' },
+  opcintype => 'text', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'time_ops', opcfamily => 'btree/time_ops',
-  opcintype => 'time' },
+  opcintype => 'time', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'time_ops', opcfamily => 'hash/time_ops',
-  opcintype => 'time' },
+  opcintype => 'time', opcisbitwise => 't'},
 { oid => '3127', oid_symbol => 'TIMESTAMPTZ_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'timestamptz_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'timestamptz' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'timestamptz', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'timestamptz_ops',
-  opcfamily => 'hash/timestamptz_ops', opcintype => 'timestamptz' },
+  opcfamily => 'hash/timestamptz_ops', opcintype => 'timestamptz', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'timetz_ops',
-  opcfamily => 'btree/timetz_ops', opcintype => 'timetz' },
+  opcfamily => 'btree/timetz_ops', opcintype => 'timetz', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'timetz_ops', opcfamily => 'hash/timetz_ops',
-  opcintype => 'timetz' },
+  opcintype => 'timetz', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'varbit_ops',
-  opcfamily => 'btree/varbit_ops', opcintype => 'varbit' },
+  opcfamily => 'btree/varbit_ops', opcintype => 'varbit', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'varchar_ops', opcfamily => 'btree/text_ops',
-  opcintype => 'text', opcdefault => 'f' },
+  opcintype => 'text', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'varchar_ops', opcfamily => 'hash/text_ops',
-  opcintype => 'text', opcdefault => 'f' },
+  opcintype => 'text', opcdefault => 'f', opcisbitwise => 't'},
 { oid => '3128', oid_symbol => 'TIMESTAMP_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'timestamp_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'timestamp' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'timestamp', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'timestamp_ops',
-  opcfamily => 'hash/timestamp_ops', opcintype => 'timestamp' },
+  opcfamily => 'hash/timestamp_ops', opcintype => 'timestamp', opcisbitwise => 't'},
 { oid => '4217', oid_symbol => 'TEXT_BTREE_PATTERN_OPS_OID',
   opcmethod => 'btree', opcname => 'text_pattern_ops',
   opcfamily => 'btree/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { oid => '4218', oid_symbol => 'VARCHAR_BTREE_PATTERN_OPS_OID',
   opcmethod => 'btree', opcname => 'varchar_pattern_ops',
   opcfamily => 'btree/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { oid => '4219', oid_symbol => 'BPCHAR_BTREE_PATTERN_OPS_OID',
   opcmethod => 'btree', opcname => 'bpchar_pattern_ops',
   opcfamily => 'btree/bpchar_pattern_ops', opcintype => 'bpchar',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'money_ops', opcfamily => 'btree/money_ops',
-  opcintype => 'money' },
+  opcintype => 'money', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bool_ops', opcfamily => 'hash/bool_ops',
-  opcintype => 'bool' },
+  opcintype => 'bool', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bytea_ops', opcfamily => 'hash/bytea_ops',
-  opcintype => 'bytea' },
+  opcintype => 'bytea', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'tid_ops', opcfamily => 'btree/tid_ops',
-  opcintype => 'tid' },
+  opcintype => 'tid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'xid_ops', opcfamily => 'hash/xid_ops',
-  opcintype => 'xid' },
+  opcintype => 'xid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'cid_ops', opcfamily => 'hash/cid_ops',
-  opcintype => 'cid' },
+  opcintype => 'cid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'tid_ops', opcfamily => 'hash/tid_ops',
-  opcintype => 'tid' },
+  opcintype => 'tid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'text_pattern_ops',
   opcfamily => 'hash/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'varchar_pattern_ops',
   opcfamily => 'hash/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bpchar_pattern_ops',
   opcfamily => 'hash/bpchar_pattern_ops', opcintype => 'bpchar',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'aclitem_ops',
-  opcfamily => 'hash/aclitem_ops', opcintype => 'aclitem' },
+  opcfamily => 'hash/aclitem_ops', opcintype => 'aclitem', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'box_ops', opcfamily => 'gist/box_ops',
-  opcintype => 'box' },
+  opcintype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'point_ops', opcfamily => 'gist/point_ops',
-  opcintype => 'point', opckeytype => 'box' },
+  opcintype => 'point', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'poly_ops', opcfamily => 'gist/poly_ops',
-  opcintype => 'polygon', opckeytype => 'box' },
+  opcintype => 'polygon', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'circle_ops', opcfamily => 'gist/circle_ops',
-  opcintype => 'circle', opckeytype => 'box' },
+  opcintype => 'circle', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'array_ops', opcfamily => 'gin/array_ops',
-  opcintype => 'anyarray', opckeytype => 'anyelement' },
+  opcintype => 'anyarray', opckeytype => 'anyelement', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'uuid_ops', opcfamily => 'btree/uuid_ops',
-  opcintype => 'uuid' },
+  opcintype => 'uuid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'uuid_ops', opcfamily => 'hash/uuid_ops',
-  opcintype => 'uuid' },
+  opcintype => 'uuid', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'pg_lsn_ops',
-  opcfamily => 'btree/pg_lsn_ops', opcintype => 'pg_lsn' },
+  opcfamily => 'btree/pg_lsn_ops', opcintype => 'pg_lsn', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'pg_lsn_ops', opcfamily => 'hash/pg_lsn_ops',
-  opcintype => 'pg_lsn' },
+  opcintype => 'pg_lsn', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'enum_ops', opcfamily => 'btree/enum_ops',
-  opcintype => 'anyenum' },
+  opcintype => 'anyenum', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'enum_ops', opcfamily => 'hash/enum_ops',
-  opcintype => 'anyenum' },
+  opcintype => 'anyenum', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'tsvector_ops',
-  opcfamily => 'btree/tsvector_ops', opcintype => 'tsvector' },
+  opcfamily => 'btree/tsvector_ops', opcintype => 'tsvector', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'tsvector_ops',
   opcfamily => 'gist/tsvector_ops', opcintype => 'tsvector',
-  opckeytype => 'gtsvector' },
+  opckeytype => 'gtsvector', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'tsvector_ops',
   opcfamily => 'gin/tsvector_ops', opcintype => 'tsvector',
-  opckeytype => 'text' },
+  opckeytype => 'text', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'tsquery_ops',
-  opcfamily => 'btree/tsquery_ops', opcintype => 'tsquery' },
+  opcfamily => 'btree/tsquery_ops', opcintype => 'tsquery', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'tsquery_ops',
   opcfamily => 'gist/tsquery_ops', opcintype => 'tsquery',
-  opckeytype => 'int8' },
+  opckeytype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'range_ops', opcfamily => 'btree/range_ops',
-  opcintype => 'anyrange' },
+  opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'range_ops', opcfamily => 'hash/range_ops',
-  opcintype => 'anyrange' },
+  opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'range_ops', opcfamily => 'gist/range_ops',
-  opcintype => 'anyrange' },
+  opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'range_ops',
-  opcfamily => 'spgist/range_ops', opcintype => 'anyrange' },
+  opcfamily => 'spgist/range_ops', opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'box_ops', opcfamily => 'spgist/box_ops',
-  opcintype => 'box' },
+  opcintype => 'box', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'quad_point_ops',
-  opcfamily => 'spgist/quad_point_ops', opcintype => 'point' },
+  opcfamily => 'spgist/quad_point_ops', opcintype => 'point', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'kd_point_ops',
-  opcfamily => 'spgist/kd_point_ops', opcintype => 'point', opcdefault => 'f' },
+  opcfamily => 'spgist/kd_point_ops', opcintype => 'point', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'text_ops', opcfamily => 'spgist/text_ops',
-  opcintype => 'text' },
+  opcintype => 'text', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'poly_ops', opcfamily => 'spgist/poly_ops',
-  opcintype => 'polygon', opckeytype => 'box' },
+  opcintype => 'polygon', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'jsonb_ops', opcfamily => 'btree/jsonb_ops',
-  opcintype => 'jsonb' },
+  opcintype => 'jsonb', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'jsonb_ops', opcfamily => 'hash/jsonb_ops',
-  opcintype => 'jsonb' },
+  opcintype => 'jsonb', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'jsonb_ops', opcfamily => 'gin/jsonb_ops',
-  opcintype => 'jsonb', opckeytype => 'text' },
+  opcintype => 'jsonb', opckeytype => 'text', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'jsonb_path_ops',
   opcfamily => 'gin/jsonb_path_ops', opcintype => 'jsonb', opcdefault => 'f',
-  opckeytype => 'int4' },
+  opckeytype => 'int4', opcisbitwise => 't'},
 
 # BRIN operator classes
 
@@ -252,94 +254,94 @@
 
 { opcmethod => 'brin', opcname => 'bytea_minmax_ops',
   opcfamily => 'brin/bytea_minmax_ops', opcintype => 'bytea',
-  opckeytype => 'bytea' },
+  opckeytype => 'bytea', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'char_minmax_ops',
   opcfamily => 'brin/char_minmax_ops', opcintype => 'char',
-  opckeytype => 'char' },
+  opckeytype => 'char', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'name_minmax_ops',
   opcfamily => 'brin/name_minmax_ops', opcintype => 'name',
-  opckeytype => 'name' },
+  opckeytype => 'name', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'int8_minmax_ops',
   opcfamily => 'brin/integer_minmax_ops', opcintype => 'int8',
-  opckeytype => 'int8' },
+  opckeytype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'int2_minmax_ops',
   opcfamily => 'brin/integer_minmax_ops', opcintype => 'int2',
-  opckeytype => 'int2' },
+  opckeytype => 'int2', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'int4_minmax_ops',
   opcfamily => 'brin/integer_minmax_ops', opcintype => 'int4',
-  opckeytype => 'int4' },
+  opckeytype => 'int4', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'text_minmax_ops',
   opcfamily => 'brin/text_minmax_ops', opcintype => 'text',
-  opckeytype => 'text' },
+  opckeytype => 'text', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'oid_minmax_ops',
-  opcfamily => 'brin/oid_minmax_ops', opcintype => 'oid', opckeytype => 'oid' },
+  opcfamily => 'brin/oid_minmax_ops', opcintype => 'oid', opckeytype => 'oid', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'tid_minmax_ops',
-  opcfamily => 'brin/tid_minmax_ops', opcintype => 'tid', opckeytype => 'tid' },
+  opcfamily => 'brin/tid_minmax_ops', opcintype => 'tid', opckeytype => 'tid', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'float4_minmax_ops',
   opcfamily => 'brin/float_minmax_ops', opcintype => 'float4',
-  opckeytype => 'float4' },
+  opckeytype => 'float4', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'float8_minmax_ops',
   opcfamily => 'brin/float_minmax_ops', opcintype => 'float8',
-  opckeytype => 'float8' },
+  opckeytype => 'float8', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'macaddr_minmax_ops',
   opcfamily => 'brin/macaddr_minmax_ops', opcintype => 'macaddr',
-  opckeytype => 'macaddr' },
+  opckeytype => 'macaddr', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'macaddr8_minmax_ops',
   opcfamily => 'brin/macaddr8_minmax_ops', opcintype => 'macaddr8',
-  opckeytype => 'macaddr8' },
+  opckeytype => 'macaddr8', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'inet_minmax_ops',
   opcfamily => 'brin/network_minmax_ops', opcintype => 'inet',
-  opcdefault => 'f', opckeytype => 'inet' },
+  opcdefault => 'f', opckeytype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'inet_inclusion_ops',
   opcfamily => 'brin/network_inclusion_ops', opcintype => 'inet',
-  opckeytype => 'inet' },
+  opckeytype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'bpchar_minmax_ops',
   opcfamily => 'brin/bpchar_minmax_ops', opcintype => 'bpchar',
-  opckeytype => 'bpchar' },
+  opckeytype => 'bpchar', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'time_minmax_ops',
   opcfamily => 'brin/time_minmax_ops', opcintype => 'time',
-  opckeytype => 'time' },
+  opckeytype => 'time', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'date_minmax_ops',
   opcfamily => 'brin/datetime_minmax_ops', opcintype => 'date',
-  opckeytype => 'date' },
+  opckeytype => 'date', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'timestamp_minmax_ops',
   opcfamily => 'brin/datetime_minmax_ops', opcintype => 'timestamp',
-  opckeytype => 'timestamp' },
+  opckeytype => 'timestamp', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'timestamptz_minmax_ops',
   opcfamily => 'brin/datetime_minmax_ops', opcintype => 'timestamptz',
-  opckeytype => 'timestamptz' },
+  opckeytype => 'timestamptz', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'interval_minmax_ops',
   opcfamily => 'brin/interval_minmax_ops', opcintype => 'interval',
-  opckeytype => 'interval' },
+  opckeytype => 'interval', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'timetz_minmax_ops',
   opcfamily => 'brin/timetz_minmax_ops', opcintype => 'timetz',
-  opckeytype => 'timetz' },
+  opckeytype => 'timetz', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'bit_minmax_ops',
-  opcfamily => 'brin/bit_minmax_ops', opcintype => 'bit', opckeytype => 'bit' },
+  opcfamily => 'brin/bit_minmax_ops', opcintype => 'bit', opckeytype => 'bit', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'varbit_minmax_ops',
   opcfamily => 'brin/varbit_minmax_ops', opcintype => 'varbit',
-  opckeytype => 'varbit' },
+  opckeytype => 'varbit', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'numeric_minmax_ops',
   opcfamily => 'brin/numeric_minmax_ops', opcintype => 'numeric',
-  opckeytype => 'numeric' },
+  opckeytype => 'numeric', opcisbitwise => 't'},
 
 # no brin opclass for record, anyarray
 
 { opcmethod => 'brin', opcname => 'uuid_minmax_ops',
   opcfamily => 'brin/uuid_minmax_ops', opcintype => 'uuid',
-  opckeytype => 'uuid' },
+  opckeytype => 'uuid', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'range_inclusion_ops',
   opcfamily => 'brin/range_inclusion_ops', opcintype => 'anyrange',
-  opckeytype => 'anyrange' },
+  opckeytype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'pg_lsn_minmax_ops',
   opcfamily => 'brin/pg_lsn_minmax_ops', opcintype => 'pg_lsn',
-  opckeytype => 'pg_lsn' },
+  opckeytype => 'pg_lsn', opcisbitwise => 't'},
 
 # no brin opclass for enum, tsvector, tsquery, jsonb
 
 { opcmethod => 'brin', opcname => 'box_inclusion_ops',
   opcfamily => 'brin/box_inclusion_ops', opcintype => 'box',
-  opckeytype => 'box' },
+  opckeytype => 'box', opcisbitwise => 't'},
 
 # no brin opclass for the geometric types except box
 
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 84853c1..9ab32b3 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -73,6 +73,9 @@ CATALOG(pg_opclass,2616,OperatorClassRelationId)
 
 	/* type of data in index, or InvalidOid */
 	Oid			opckeytype BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
+
+	/* T if opclass equality also means "bitwise equality" */
+	bool		opcisbitwise BKI_DEFAULT(f);
 } FormData_pg_opclass;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a..4ab4cc7 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2577,6 +2577,7 @@ typedef struct CreateOpClassStmt
 	TypeName   *datatype;		/* datatype of indexed column */
 	List	   *items;			/* List of CreateOpClassItem nodes */
 	bool		isDefault;		/* Should be marked as default for type? */
+	bool		isBitwise;		/* Is opclass equality bitwise? */
 } CreateOpClassStmt;
 
 #define OPCLASS_ITEM_OPERATOR		1
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace84..d6a8e8f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -58,6 +58,7 @@ PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
 PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD)
 PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD)
+PG_KEYWORD("bitwise", BITWISE, UNRESERVED_KEYWORD)
 PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD)
 PG_KEYWORD("both", BOTH, RESERVED_KEYWORD)
 PG_KEYWORD("by", BY, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
index 84da403..241b61c 100644
--- a/src/test/regress/expected/create_am.out
+++ b/src/test/regress/expected/create_am.out
@@ -13,7 +13,7 @@ CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base);
 ERROR:  data type box has no default operator class for access method "gist2"
 HINT:  You must specify an operator class for the index or define a default operator class for the data type.
 -- Make operator class for boxes using gist2
-CREATE OPERATOR CLASS box_ops DEFAULT
+CREATE OPERATOR CLASS box_ops DEFAULT BITWISE
 	FOR TYPE box USING gist2 AS
 	OPERATOR 1	<<,
 	OPERATOR 2	&<,
diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index a7f6de7..46b5ca0 100644
--- a/src/test/regress/sql/create_am.sql
+++ b/src/test/regress/sql/create_am.sql
@@ -14,7 +14,7 @@ CREATE ACCESS METHOD bogus TYPE INDEX HANDLER heap_tableam_handler;
 CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base);
 
 -- Make operator class for boxes using gist2
-CREATE OPERATOR CLASS box_ops DEFAULT
+CREATE OPERATOR CLASS box_ops DEFAULT BITWISE
 	FOR TYPE box USING gist2 AS
 	OPERATOR 1	<<,
 	OPERATOR 2	&<,
In reply to: Anastasia Lubennikova (#12)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Mon, Oct 28, 2019 at 11:11 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

At first I implemented bitwise as default, because it is more common .
Though, I agree that it's essential to avoid false positives here.
The new version of the patch is attached. I also updated pg_dump.

A few more open questions:
1) How to handle contrib modules that create new opclasses?
Since default is 'not bitwise' it means that various operator classes
created in extensions
such as bloom, btree_gin and others, won't be able to take advantage of
various optimizations
that require the opclass to be BITWISE.

What optimizations? Do we anticipate that other index AMs will benefit
from BITWISE-ness?

'v2-Opclass-bitwise-equality-0002' patch simply adds BITWISE keyword
where necessary.

2) Whether we should provide ALTER OPERATOR CLASS SET BITWISE syntax?

I think that that's probably not desirable. There should at least be a
strong practical advantage if we go that way. This would mean ALTER
OPERATOR CLASS could change the "substance" of an opclass, which is
fundamentally different from what it can do already (it currently just
changes the owner, or the schema that it is stored in).

3) Current patch modifies regression test so that it checks CREATE
OPCLASS BITWISE syntax.
Is there anything else worth testing? As I see it, this patch is just
about infrastructure changes,
and more specific tests will be added by features that will implement
further optimizations.

I think so too -- this is really about associating a single piece of
information with an operator class.

BTW: No need to bump catversion when posting a patch, which I see in
"v2-Opclass-*0001.patch". That is our policy. (A catversion bump is
generally supposed to be done at the last minute, just as the patch is
committed. This avoids unnecessary conflicts against the master branch
over time, as a patch is developed.)

--
Peter Geoghegan

#14Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Peter Geoghegan (#13)
2 attachment(s)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

14.11.2019 0:25, Peter Geoghegan wrote:

On Mon, Oct 28, 2019 at 11:11 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

At first I implemented bitwise as default, because it is more common .
Though, I agree that it's essential to avoid false positives here.
The new version of the patch is attached. I also updated pg_dump.

A few more open questions:
1) How to handle contrib modules that create new opclasses?
Since default is 'not bitwise' it means that various operator classes
created in extensions
such as bloom, btree_gin and others, won't be able to take advantage of
various optimizations
that require the opclass to be BITWISE.

What optimizations? Do we anticipate that other index AMs will benefit
from BITWISE-ness?

I was thinking of possible planner optimizations, that Tom mentioned up
thread.
Though, I don't have any specific examples. Anyway, we can implement
support for user-defined opclasses later.

3) Current patch modifies regression test so that it checks CREATE
OPCLASS BITWISE syntax.
Is there anything else worth testing? As I see it, this patch is just
about infrastructure changes,
and more specific tests will be added by features that will implement
further optimizations.

I think so too -- this is really about associating a single piece of
information with an operator class.

Great. It seems that the patch is ready for commit.
I attached new version with pg_opclass documentation update.

One more thing I am uncertain about  is array_ops. Arrays may contain
bitwise and not bitwise element types.
What is the correct value of opcisbitwise the array_ops itself?

--
Anastasia Lubennikova
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v3-Opclass-bitwise-equality-0002.patchtext/x-patch; name=v3-Opclass-bitwise-equality-0002.patchDownload
commit cc1fdc36549e4596e769fa268b8bd9350707f9f2
Author: Anastasia <a.lubennikova@postgrespro.ru>
Date:   Fri Nov 15 14:57:50 2019 +0300

    v3-Opclass-bitwise-equality-0002.patch

diff --git a/contrib/bloom/bloom--1.0.sql b/contrib/bloom/bloom--1.0.sql
index 4e7c922..ca54f81 100644
--- a/contrib/bloom/bloom--1.0.sql
+++ b/contrib/bloom/bloom--1.0.sql
@@ -15,11 +15,11 @@ COMMENT ON ACCESS METHOD bloom IS 'bloom index access method';
 -- Opclasses
 
 CREATE OPERATOR CLASS int4_ops
-DEFAULT FOR TYPE int4 USING bloom AS
+DEFAULT BITWISE FOR TYPE int4 USING bloom AS
 	OPERATOR	1	=(int4, int4),
 	FUNCTION	1	hashint4(int4);
 
 CREATE OPERATOR CLASS text_ops
-DEFAULT FOR TYPE text USING bloom AS
+DEFAULT BITWISE FOR TYPE text USING bloom AS
 	OPERATOR	1	=(text, text),
 	FUNCTION	1	hashtext(text);
diff --git a/contrib/btree_gin/btree_gin--1.0--1.1.sql b/contrib/btree_gin/btree_gin--1.0--1.1.sql
index dd81d27..b6f01cb 100644
--- a/contrib/btree_gin/btree_gin--1.0--1.1.sql
+++ b/contrib/btree_gin/btree_gin--1.0--1.1.sql
@@ -20,7 +20,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS macaddr8_ops
-DEFAULT FOR TYPE macaddr8 USING gin
+DEFAULT BITWISE FOR TYPE macaddr8 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gin/btree_gin--1.0.sql b/contrib/btree_gin/btree_gin--1.0.sql
index cf867ef..473d395 100644
--- a/contrib/btree_gin/btree_gin--1.0.sql
+++ b/contrib/btree_gin/btree_gin--1.0.sql
@@ -24,7 +24,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS int2_ops
-DEFAULT FOR TYPE int2 USING gin
+DEFAULT BITWISE FOR TYPE int2 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -54,7 +54,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS int4_ops
-DEFAULT FOR TYPE int4 USING gin
+DEFAULT BITWISE FOR TYPE int4 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -84,7 +84,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS int8_ops
-DEFAULT FOR TYPE int8 USING gin
+DEFAULT BITWISE FOR TYPE int8 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -114,7 +114,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS float4_ops
-DEFAULT FOR TYPE float4 USING gin
+DEFAULT BITWISE FOR TYPE float4 USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -174,7 +174,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS money_ops
-DEFAULT FOR TYPE money USING gin
+DEFAULT BITWISE FOR TYPE money USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -204,7 +204,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS oid_ops
-DEFAULT FOR TYPE oid USING gin
+DEFAULT BITWISE FOR TYPE oid USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -234,7 +234,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS timestamp_ops
-DEFAULT FOR TYPE timestamp USING gin
+DEFAULT BITWISE FOR TYPE timestamp USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -264,7 +264,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gin
+DEFAULT BITWISE FOR TYPE timestamptz USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -294,7 +294,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS time_ops
-DEFAULT FOR TYPE time USING gin
+DEFAULT BITWISE FOR TYPE time USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -324,7 +324,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS timetz_ops
-DEFAULT FOR TYPE timetz USING gin
+DEFAULT BITWISE FOR TYPE timetz USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -354,7 +354,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS date_ops
-DEFAULT FOR TYPE date USING gin
+DEFAULT BITWISE FOR TYPE date USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -384,7 +384,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS interval_ops
-DEFAULT FOR TYPE interval USING gin
+DEFAULT BITWISE FOR TYPE interval USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -414,7 +414,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS macaddr_ops
-DEFAULT FOR TYPE macaddr USING gin
+DEFAULT BITWISE FOR TYPE macaddr USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -444,7 +444,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS inet_ops
-DEFAULT FOR TYPE inet USING gin
+DEFAULT BITWISE FOR TYPE inet USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -474,7 +474,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS cidr_ops
-DEFAULT FOR TYPE cidr USING gin
+DEFAULT BITWISE FOR TYPE cidr USING gin
 AS
     OPERATOR        1       <(inet,inet),
     OPERATOR        2       <=(inet,inet),
@@ -504,7 +504,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS text_ops
-DEFAULT FOR TYPE text USING gin
+DEFAULT BITWISE FOR TYPE text USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -519,7 +519,7 @@ AS
 STORAGE         text;
 
 CREATE OPERATOR CLASS varchar_ops
-DEFAULT FOR TYPE varchar USING gin
+DEFAULT BITWISE FOR TYPE varchar USING gin
 AS
     OPERATOR        1       <(text,text),
     OPERATOR        2       <=(text,text),
@@ -549,7 +549,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS char_ops
-DEFAULT FOR TYPE "char" USING gin
+DEFAULT BITWISE FOR TYPE "char" USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -579,7 +579,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bytea_ops
-DEFAULT FOR TYPE bytea USING gin
+DEFAULT BITWISE FOR TYPE bytea USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -609,7 +609,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bit_ops
-DEFAULT FOR TYPE bit USING gin
+DEFAULT BITWISE FOR TYPE bit USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -639,7 +639,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS varbit_ops
-DEFAULT FOR TYPE varbit USING gin
+DEFAULT BITWISE FOR TYPE varbit USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gin/btree_gin--1.1--1.2.sql b/contrib/btree_gin/btree_gin--1.1--1.2.sql
index 2a16837..f8bd1b4 100644
--- a/contrib/btree_gin/btree_gin--1.1--1.2.sql
+++ b/contrib/btree_gin/btree_gin--1.1--1.2.sql
@@ -32,7 +32,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS enum_ops
-DEFAULT FOR TYPE anyenum USING gin
+DEFAULT BITWISE FOR TYPE anyenum USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gin/btree_gin--1.2--1.3.sql b/contrib/btree_gin/btree_gin--1.2--1.3.sql
index db675b7..1c5d538 100644
--- a/contrib/btree_gin/btree_gin--1.2--1.3.sql
+++ b/contrib/btree_gin/btree_gin--1.2--1.3.sql
@@ -20,7 +20,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS uuid_ops
-DEFAULT FOR TYPE uuid USING gin
+DEFAULT BITWISE FOR TYPE uuid USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -51,7 +51,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS name_ops
-DEFAULT FOR TYPE name USING gin
+DEFAULT BITWISE FOR TYPE name USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -82,7 +82,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bool_ops
-DEFAULT FOR TYPE bool USING gin
+DEFAULT BITWISE FOR TYPE bool USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
@@ -113,7 +113,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE;
 
 CREATE OPERATOR CLASS bpchar_ops
-DEFAULT FOR TYPE bpchar USING gin
+DEFAULT BITWISE FOR TYPE bpchar USING gin
 AS
     OPERATOR        1       <,
     OPERATOR        2       <=,
diff --git a/contrib/btree_gist/btree_gist--1.2--1.3.sql b/contrib/btree_gist/btree_gist--1.2--1.3.sql
index 726561e..552339c 100644
--- a/contrib/btree_gist/btree_gist--1.2--1.3.sql
+++ b/contrib/btree_gist/btree_gist--1.2--1.3.sql
@@ -43,7 +43,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_uuid_ops
-DEFAULT FOR TYPE uuid USING gist
+DEFAULT BITWISE FOR TYPE uuid USING gist
 AS
 	OPERATOR	1	<   ,
 	OPERATOR	2	<=  ,
diff --git a/contrib/btree_gist/btree_gist--1.2.sql b/contrib/btree_gist/btree_gist--1.2.sql
index 1efe753..8a705b0 100644
--- a/contrib/btree_gist/btree_gist--1.2.sql
+++ b/contrib/btree_gist/btree_gist--1.2.sql
@@ -296,7 +296,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_oid_ops
-DEFAULT FOR TYPE oid USING gist
+DEFAULT BITWISE FOR TYPE oid USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -373,7 +373,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_int2_ops
-DEFAULT FOR TYPE int2 USING gist
+DEFAULT BITWISE FOR TYPE int2 USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -445,7 +445,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_int4_ops
-DEFAULT FOR TYPE int4 USING gist
+DEFAULT BITWISE FOR TYPE int4 USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -518,7 +518,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_int8_ops
-DEFAULT FOR TYPE int8 USING gist
+DEFAULT BITWISE FOR TYPE int8 USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -749,7 +749,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_timestamp_ops
-DEFAULT FOR TYPE timestamp USING gist
+DEFAULT BITWISE FOR TYPE timestamp USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -773,7 +773,7 @@ ALTER OPERATOR FAMILY gist_timestamp_ops USING gist ADD
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_timestamptz_ops
-DEFAULT FOR TYPE timestamptz USING gist
+DEFAULT BITWISE FOR TYPE timestamptz USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -855,7 +855,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_time_ops
-DEFAULT FOR TYPE time USING gist
+DEFAULT BITWISE FOR TYPE time USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -879,7 +879,7 @@ ALTER OPERATOR FAMILY gist_time_ops USING gist ADD
 
 
 CREATE OPERATOR CLASS gist_timetz_ops
-DEFAULT FOR TYPE timetz USING gist
+DEFAULT BITWISE FOR TYPE timetz USING gist
 AS
 	OPERATOR	1	<   ,
 	OPERATOR	2	<=  ,
@@ -950,7 +950,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_date_ops
-DEFAULT FOR TYPE date USING gist
+DEFAULT BITWISE FOR TYPE date USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1028,7 +1028,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_interval_ops
-DEFAULT FOR TYPE interval USING gist
+DEFAULT BITWISE FOR TYPE interval USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
@@ -1101,7 +1101,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_cash_ops
-DEFAULT FOR TYPE money USING gist
+DEFAULT BITWISE FOR TYPE money USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
@@ -1169,7 +1169,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_macaddr_ops
-DEFAULT FOR TYPE macaddr USING gist
+DEFAULT BITWISE FOR TYPE macaddr USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
@@ -1240,7 +1240,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_text_ops
-DEFAULT FOR TYPE text USING gist
+DEFAULT BITWISE FOR TYPE text USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1263,7 +1263,7 @@ ALTER OPERATOR FAMILY gist_text_ops USING gist ADD
 
 ---- Create the operator class
 CREATE OPERATOR CLASS gist_bpchar_ops
-DEFAULT FOR TYPE bpchar USING gist
+DEFAULT BITWISE FOR TYPE bpchar USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1322,7 +1322,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_bytea_ops
-DEFAULT FOR TYPE bytea USING gist
+DEFAULT BITWISE FOR TYPE bytea USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1443,7 +1443,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_bit_ops
-DEFAULT FOR TYPE bit USING gist
+DEFAULT BITWISE FOR TYPE bit USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1466,7 +1466,7 @@ ALTER OPERATOR FAMILY gist_bit_ops USING gist ADD
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_vbit_ops
-DEFAULT FOR TYPE varbit USING gist
+DEFAULT BITWISE FOR TYPE varbit USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
@@ -1527,7 +1527,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_inet_ops
-DEFAULT FOR TYPE inet USING gist
+DEFAULT BITWISE FOR TYPE inet USING gist
 AS
 	OPERATOR	1	<   ,
 	OPERATOR	2	<=  ,
@@ -1549,7 +1549,7 @@ ALTER OPERATOR FAMILY gist_inet_ops USING gist ADD
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_cidr_ops
-DEFAULT FOR TYPE cidr USING gist
+DEFAULT BITWISE FOR TYPE cidr USING gist
 AS
 	OPERATOR	1	<  (inet, inet)  ,
 	OPERATOR	2	<= (inet, inet)  ,
diff --git a/contrib/btree_gist/btree_gist--1.3--1.4.sql b/contrib/btree_gist/btree_gist--1.3--1.4.sql
index f77f6c8..557701a 100644
--- a/contrib/btree_gist/btree_gist--1.3--1.4.sql
+++ b/contrib/btree_gist/btree_gist--1.3--1.4.sql
@@ -43,7 +43,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_macaddr8_ops
-DEFAULT FOR TYPE macaddr8 USING gist
+DEFAULT BITWISE FOR TYPE macaddr8 USING gist
 AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
diff --git a/contrib/btree_gist/btree_gist--1.4--1.5.sql b/contrib/btree_gist/btree_gist--1.4--1.5.sql
index cf974c2..14ef593 100644
--- a/contrib/btree_gist/btree_gist--1.4--1.5.sql
+++ b/contrib/btree_gist/btree_gist--1.4--1.5.sql
@@ -48,7 +48,7 @@ LANGUAGE C IMMUTABLE STRICT;
 
 -- Create the operator class
 CREATE OPERATOR CLASS gist_enum_ops
-DEFAULT FOR TYPE anyenum USING gist
+DEFAULT BITWISE FOR TYPE anyenum USING gist
 AS
 	OPERATOR	1	<  ,
 	OPERATOR	2	<= ,
diff --git a/contrib/citext/citext--1.4--1.5.sql b/contrib/citext/citext--1.4--1.5.sql
index 5ae522b..6a3369e 100644
--- a/contrib/citext/citext--1.4--1.5.sql
+++ b/contrib/citext/citext--1.4--1.5.sql
@@ -79,7 +79,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
 
 CREATE OPERATOR CLASS citext_pattern_ops
-FOR TYPE CITEXT USING btree AS
+BITWISE FOR TYPE CITEXT USING btree AS
     OPERATOR    1   ~<~  (citext, citext),
     OPERATOR    2   ~<=~ (citext, citext),
     OPERATOR    3   =    (citext, citext),
diff --git a/contrib/citext/citext--1.4.sql b/contrib/citext/citext--1.4.sql
index 7b06198..dfed78b 100644
--- a/contrib/citext/citext--1.4.sql
+++ b/contrib/citext/citext--1.4.sql
@@ -208,7 +208,7 @@ LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
 --
 
 CREATE OPERATOR CLASS citext_ops
-DEFAULT FOR TYPE CITEXT USING btree AS
+DEFAULT BITWISE FOR TYPE CITEXT USING btree AS
     OPERATOR    1   <  (citext, citext),
     OPERATOR    2   <= (citext, citext),
     OPERATOR    3   =  (citext, citext),
@@ -221,7 +221,7 @@ DEFAULT FOR TYPE CITEXT USING btree AS
 --
 
 CREATE OPERATOR CLASS citext_ops
-DEFAULT FOR TYPE citext USING hash AS
+DEFAULT BITWISE FOR TYPE citext USING hash AS
     OPERATOR    1   =  (citext, citext),
     FUNCTION    1   citext_hash(citext);
 
diff --git a/contrib/cube/cube--1.2.sql b/contrib/cube/cube--1.2.sql
index dea2614..faf5a06 100644
--- a/contrib/cube/cube--1.2.sql
+++ b/contrib/cube/cube--1.2.sql
@@ -347,7 +347,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- Create the operator classes for indexing
 
 CREATE OPERATOR CLASS cube_ops
-    DEFAULT FOR TYPE cube USING btree AS
+    DEFAULT BITWISE FOR TYPE cube USING btree AS
         OPERATOR        1       < ,
         OPERATOR        2       <= ,
         OPERATOR        3       = ,
@@ -356,7 +356,7 @@ CREATE OPERATOR CLASS cube_ops
         FUNCTION        1       cube_cmp(cube, cube);
 
 CREATE OPERATOR CLASS gist_cube_ops
-    DEFAULT FOR TYPE cube USING gist AS
+    DEFAULT BITWISE FOR TYPE cube USING gist AS
 	OPERATOR	3	&& ,
 	OPERATOR	6	= ,
 	OPERATOR	7	@> ,
diff --git a/contrib/hstore/hstore--1.4.sql b/contrib/hstore/hstore--1.4.sql
index 4294d14..d34896b 100644
--- a/contrib/hstore/hstore--1.4.sql
+++ b/contrib/hstore/hstore--1.4.sql
@@ -423,7 +423,7 @@ CREATE OPERATOR #>=# (
 );
 
 CREATE OPERATOR CLASS btree_hstore_ops
-DEFAULT FOR TYPE hstore USING btree
+DEFAULT BITWISE FOR TYPE hstore USING btree
 AS
 	OPERATOR	1	#<# ,
 	OPERATOR	2	#<=# ,
@@ -440,7 +440,7 @@ AS 'MODULE_PATHNAME','hstore_hash'
 LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;
 
 CREATE OPERATOR CLASS hash_hstore_ops
-DEFAULT FOR TYPE hstore USING hash
+DEFAULT BITWISE FOR TYPE hstore USING hash
 AS
 	OPERATOR	1	= ,
 	FUNCTION	1	hstore_hash(hstore);
@@ -501,7 +501,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gist_hstore_ops
-DEFAULT FOR TYPE hstore USING gist
+DEFAULT BITWISE FOR TYPE hstore USING gist
 AS
 	OPERATOR        7       @> ,
 	OPERATOR        9       ?(hstore,text) ,
@@ -537,7 +537,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gin_hstore_ops
-DEFAULT FOR TYPE hstore USING gin
+DEFAULT BITWISE FOR TYPE hstore USING gin
 AS
 	OPERATOR        7       @>,
 	OPERATOR        9       ?(hstore,text),
diff --git a/contrib/intarray/intarray--1.2.sql b/contrib/intarray/intarray--1.2.sql
index f10b53d..4cca844 100644
--- a/contrib/intarray/intarray--1.2.sql
+++ b/contrib/intarray/intarray--1.2.sql
@@ -397,7 +397,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- Create the operator class for indexing
 
 CREATE OPERATOR CLASS gist__int_ops
-DEFAULT FOR TYPE _int4 USING gist AS
+DEFAULT BITWISE FOR TYPE _int4 USING gist AS
 	OPERATOR	3	&&,
 	OPERATOR	6	= (anyarray, anyarray),
 	OPERATOR	7	@>,
@@ -473,7 +473,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- register the opclass for indexing (not as default)
 
 CREATE OPERATOR CLASS gist__intbig_ops
-FOR TYPE _int4 USING gist
+BITWISE FOR TYPE _int4 USING gist
 AS
 	OPERATOR	3	&&,
 	OPERATOR	6	= (anyarray, anyarray),
@@ -504,7 +504,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gin__int_ops
-FOR TYPE _int4 USING gin
+BITWISE FOR TYPE _int4 USING gin
 AS
 	OPERATOR	3	&&,
 	OPERATOR	6	= (anyarray, anyarray),
diff --git a/contrib/isn/isn--1.1.sql b/contrib/isn/isn--1.1.sql
index 5206961..f6514e3 100644
--- a/contrib/isn/isn--1.1.sql
+++ b/contrib/isn/isn--1.1.sql
@@ -2716,7 +2716,7 @@ CREATE FUNCTION btean13cmp(ean13, ean13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ean13_ops DEFAULT
+CREATE OPERATOR CLASS ean13_ops DEFAULT BITWISE
 	FOR TYPE ean13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2731,7 +2731,7 @@ CREATE FUNCTION hashean13(ean13)
 	LANGUAGE 'internal' IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ean13_ops DEFAULT
+CREATE OPERATOR CLASS ean13_ops DEFAULT BITWISE
 	FOR TYPE ean13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashean13(ean13);
@@ -2842,7 +2842,7 @@ CREATE FUNCTION btisbn13cmp(isbn13, isbn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn13_ops DEFAULT
+CREATE OPERATOR CLASS isbn13_ops DEFAULT BITWISE
 	FOR TYPE isbn13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2858,7 +2858,7 @@ CREATE FUNCTION hashisbn13(isbn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn13_ops DEFAULT
+CREATE OPERATOR CLASS isbn13_ops DEFAULT BITWISE
 	FOR TYPE isbn13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashisbn13(isbn13);
@@ -2904,7 +2904,7 @@ CREATE FUNCTION btisbncmp(isbn, isbn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn_ops DEFAULT
+CREATE OPERATOR CLASS isbn_ops DEFAULT BITWISE
 	FOR TYPE isbn USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2920,7 +2920,7 @@ CREATE FUNCTION hashisbn(isbn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS isbn_ops DEFAULT
+CREATE OPERATOR CLASS isbn_ops DEFAULT BITWISE
 	FOR TYPE isbn USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashisbn(isbn);
@@ -2966,7 +2966,7 @@ CREATE FUNCTION btismn13cmp(ismn13, ismn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn13_ops DEFAULT
+CREATE OPERATOR CLASS ismn13_ops DEFAULT BITWISE
 	FOR TYPE ismn13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -2982,7 +2982,7 @@ CREATE FUNCTION hashismn13(ismn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn13_ops DEFAULT
+CREATE OPERATOR CLASS ismn13_ops DEFAULT BITWISE
 	FOR TYPE ismn13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashismn13(ismn13);
@@ -3028,7 +3028,7 @@ CREATE FUNCTION btismncmp(ismn, ismn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn_ops DEFAULT
+CREATE OPERATOR CLASS ismn_ops DEFAULT BITWISE
 	FOR TYPE ismn USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3044,7 +3044,7 @@ CREATE FUNCTION hashismn(ismn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS ismn_ops DEFAULT
+CREATE OPERATOR CLASS ismn_ops DEFAULT BITWISE
 	FOR TYPE ismn USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashismn(ismn);
@@ -3090,7 +3090,7 @@ CREATE FUNCTION btissn13cmp(issn13, issn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn13_ops DEFAULT
+CREATE OPERATOR CLASS issn13_ops DEFAULT BITWISE
 	FOR TYPE issn13 USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3106,7 +3106,7 @@ CREATE FUNCTION hashissn13(issn13)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn13_ops DEFAULT
+CREATE OPERATOR CLASS issn13_ops DEFAULT BITWISE
 	FOR TYPE issn13 USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashissn13(issn13);
@@ -3152,7 +3152,7 @@ CREATE FUNCTION btissncmp(issn, issn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn_ops DEFAULT
+CREATE OPERATOR CLASS issn_ops DEFAULT BITWISE
 	FOR TYPE issn USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3168,7 +3168,7 @@ CREATE FUNCTION hashissn(issn)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS issn_ops DEFAULT
+CREATE OPERATOR CLASS issn_ops DEFAULT BITWISE
 	FOR TYPE issn USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashissn(issn);
@@ -3214,7 +3214,7 @@ CREATE FUNCTION btupccmp(upc, upc)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS upc_ops DEFAULT
+CREATE OPERATOR CLASS upc_ops DEFAULT BITWISE
 	FOR TYPE upc USING btree FAMILY isn_ops AS
 	OPERATOR 1  <,
 	OPERATOR 2  <=,
@@ -3230,7 +3230,7 @@ CREATE FUNCTION hashupc(upc)
 	IMMUTABLE STRICT
 	PARALLEL SAFE;
 
-CREATE OPERATOR CLASS upc_ops DEFAULT
+CREATE OPERATOR CLASS upc_ops DEFAULT BITWISE
 	FOR TYPE upc USING hash FAMILY isn_ops AS
 	OPERATOR 1  =,
 	FUNCTION 1  hashupc(upc);
diff --git a/contrib/ltree/ltree--1.1.sql b/contrib/ltree/ltree--1.1.sql
index d46f5fc..3198096 100644
--- a/contrib/ltree/ltree--1.1.sql
+++ b/contrib/ltree/ltree--1.1.sql
@@ -290,7 +290,7 @@ CREATE OPERATOR || (
 -- B-tree support
 
 CREATE OPERATOR CLASS ltree_ops
-    DEFAULT FOR TYPE ltree USING btree AS
+    DEFAULT BITWISE FOR TYPE ltree USING btree AS
         OPERATOR        1       < ,
         OPERATOR        2       <= ,
         OPERATOR        3       = ,
@@ -518,7 +518,7 @@ CREATE FUNCTION ltree_same(ltree_gist, ltree_gist, internal)
 RETURNS internal as 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gist_ltree_ops
-    DEFAULT FOR TYPE ltree USING gist AS
+    DEFAULT BITWISE FOR TYPE ltree USING gist AS
 	OPERATOR	1	< ,
 	OPERATOR	2	<= ,
 	OPERATOR	3	= ,
@@ -853,7 +853,7 @@ AS 'MODULE_PATHNAME'
 LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 CREATE OPERATOR CLASS gist__ltree_ops
-    DEFAULT FOR TYPE _ltree USING gist AS
+    DEFAULT BITWISE FOR TYPE _ltree USING gist AS
 	OPERATOR	10	<@ (_ltree, ltree),
 	OPERATOR	11	@> (ltree, _ltree),
 	OPERATOR	12	~ (_ltree, lquery),
diff --git a/contrib/pg_trgm/pg_trgm--1.3.sql b/contrib/pg_trgm/pg_trgm--1.3.sql
index 5e08e87..ab917d2 100644
--- a/contrib/pg_trgm/pg_trgm--1.3.sql
+++ b/contrib/pg_trgm/pg_trgm--1.3.sql
@@ -168,7 +168,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 -- create the operator class for gist
 CREATE OPERATOR CLASS gist_trgm_ops
-FOR TYPE text USING gist
+BITWISE FOR TYPE text USING gist
 AS
         OPERATOR        1       % (text, text),
         FUNCTION        1       gtrgm_consistent (internal, text, smallint, oid, internal),
@@ -221,7 +221,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 
 -- create the operator class for gin
 CREATE OPERATOR CLASS gin_trgm_ops
-FOR TYPE text USING gin
+BITWISE FOR TYPE text USING gin
 AS
         OPERATOR        1       % (text, text),
         FUNCTION        1       btint4cmp (int4, int4),
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 14180ee..2a86210 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3111,7 +3111,7 @@ create operator public.>^ (
 create operator family my_op_family using btree;
 create function my_op_cmp(a int, b int) returns int as
   $$begin return btint4cmp(a, b); end $$ language plpgsql;
-create operator class my_op_class for type int using btree family my_op_family as
+create operator class my_op_class bitwise for type int using btree family my_op_family as
  operator 1 public.<^,
  operator 3 public.=^,
  operator 5 public.>^,
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 1c5c37b..8fabaf5 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -825,7 +825,7 @@ create operator family my_op_family using btree;
 create function my_op_cmp(a int, b int) returns int as
   $$begin return btint4cmp(a, b); end $$ language plpgsql;
 
-create operator class my_op_class for type int using btree family my_op_family as
+create operator class my_op_class bitwise for type int using btree family my_op_family as
  operator 1 public.<^,
  operator 3 public.=^,
  operator 5 public.>^,
diff --git a/contrib/seg/seg--1.1.sql b/contrib/seg/seg--1.1.sql
index d95aabc..b869071 100644
--- a/contrib/seg/seg--1.1.sql
+++ b/contrib/seg/seg--1.1.sql
@@ -365,7 +365,7 @@ LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
 -- Create the operator classes for indexing
 
 CREATE OPERATOR CLASS seg_ops
-    DEFAULT FOR TYPE seg USING btree AS
+    DEFAULT BITWISE FOR TYPE seg USING btree AS
         OPERATOR        1       < ,
         OPERATOR        2       <= ,
         OPERATOR        3       = ,
@@ -374,7 +374,7 @@ CREATE OPERATOR CLASS seg_ops
         FUNCTION        1       seg_cmp(seg, seg);
 
 CREATE OPERATOR CLASS gist_seg_ops
-DEFAULT FOR TYPE seg USING gist
+DEFAULT BITWISE FOR TYPE seg USING gist
 AS
 	OPERATOR	1	<< ,
 	OPERATOR	2	&< ,
v3-Opclass-bitwise-equality-0001.patchtext/x-patch; name=v3-Opclass-bitwise-equality-0001.patchDownload
commit 52fdfbbe14af00f93cebe872b01bd7bdd87f25c3
Author: Anastasia <a.lubennikova@postgrespro.ru>
Date:   Fri Nov 15 14:56:48 2019 +0300

    v3-Opclass-bitwise-equality-0001.patch

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 55694c4..87cc344 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4544,6 +4544,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>Type of data stored in index, or zero if same as <structfield>opcintype</structfield></entry>
      </row>
 
+     <row>
+      <entry><structfield>opcisbitwise</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if the operator class equality is the same as equivalence</entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/create_opclass.sgml b/doc/src/sgml/ref/create_opclass.sgml
index dd5252f..8d0ddfc 100644
--- a/doc/src/sgml/ref/create_opclass.sgml
+++ b/doc/src/sgml/ref/create_opclass.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
+CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT | BITWISE ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
   USING <replaceable class="parameter">index_method</replaceable> [ FAMILY <replaceable class="parameter">family_name</replaceable> ] AS
   {  OPERATOR <replaceable class="parameter">strategy_number</replaceable> <replaceable class="parameter">operator_name</replaceable> [ ( <replaceable class="parameter">op_type</replaceable>, <replaceable class="parameter">op_type</replaceable> ) ] [ FOR SEARCH | FOR ORDER BY <replaceable class="parameter">sort_family_name</replaceable> ]
    | FUNCTION <replaceable class="parameter">support_number</replaceable> [ ( <replaceable class="parameter">op_type</replaceable> [ , <replaceable class="parameter">op_type</replaceable> ] ) ] <replaceable class="parameter">function_name</replaceable> ( <replaceable class="parameter">argument_type</replaceable> [, ...] )
@@ -106,6 +106,20 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
     </listitem>
    </varlistentry>
 
+    <varlistentry>
+    <term><literal>BITWISE</literal></term>
+    <listitem>
+     <para>
+      If present, the operator class equality is the same as equivalence.
+      For example, two integers that compare equal are also binary equal,
+      while, numerics can compare equal but have different scales,
+      thus numeric opclass is not <literal>BITWISE</literal>. Even though,
+      most opclasses implement bitwise equal comparison, this behaviour
+      must be set explicitly.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">data_type</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index cb7a6bd..c0fe187 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -653,6 +653,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(typeoid);
 	values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(stmt->isDefault);
 	values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(storageoid);
+	values[Anum_pg_opclass_opcisbitwise - 1] = BoolGetDatum(stmt->isBitwise);
 
 	tup = heap_form_tuple(rel->rd_att, values, nulls);
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2f267e4..4d1793e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3785,6 +3785,7 @@ _copyCreateOpClassStmt(const CreateOpClassStmt *from)
 	COPY_NODE_FIELD(datatype);
 	COPY_NODE_FIELD(items);
 	COPY_SCALAR_FIELD(isDefault);
+	COPY_SCALAR_FIELD(isBitwise);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index da0e1d1..eac98ce 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1607,6 +1607,7 @@ _equalCreateOpClassStmt(const CreateOpClassStmt *a, const CreateOpClassStmt *b)
 	COMPARE_NODE_FIELD(datatype);
 	COMPARE_NODE_FIELD(items);
 	COMPARE_SCALAR_FIELD(isDefault);
+	COMPARE_SCALAR_FIELD(isBitwise);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2f7bd66..1fc063b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -447,7 +447,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <boolean> opt_instead
 %type <boolean> opt_unique opt_concurrently opt_verbose opt_full
-%type <boolean> opt_freeze opt_analyze opt_default opt_recheck
+%type <boolean> opt_freeze opt_analyze opt_default opt_recheck opt_bitwise
 %type <defelt>	opt_binary copy_delimiter
 
 %type <boolean> copy_from opt_program
@@ -618,7 +618,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
 	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
-	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
+	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BITWISE
 	BOOLEAN_P BOTH BY
 
 	CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
@@ -5953,16 +5953,17 @@ opt_if_not_exists: IF_P NOT EXISTS              { $$ = true; }
  *****************************************************************************/
 
 CreateOpClassStmt:
-			CREATE OPERATOR CLASS any_name opt_default FOR TYPE_P Typename
+			CREATE OPERATOR CLASS any_name opt_default opt_bitwise FOR TYPE_P Typename
 			USING access_method opt_opfamily AS opclass_item_list
 				{
 					CreateOpClassStmt *n = makeNode(CreateOpClassStmt);
 					n->opclassname = $4;
 					n->isDefault = $5;
-					n->datatype = $8;
-					n->amname = $10;
-					n->opfamilyname = $11;
-					n->items = $13;
+					n->isBitwise = $6;
+					n->datatype = $9;
+					n->amname = $11;
+					n->opfamilyname = $12;
+					n->items = $14;
 					$$ = (Node *) n;
 				}
 		;
@@ -6025,6 +6026,10 @@ opt_default:	DEFAULT						{ $$ = true; }
 			| /*EMPTY*/						{ $$ = false; }
 		;
 
+opt_bitwise:	BITWISE						{ $$ = true; }
+			| /*EMPTY*/						{ $$ = false; }
+		;
+
 opt_opfamily:	FAMILY any_name				{ $$ = $2; }
 			| /*EMPTY*/						{ $$ = NIL; }
 		;
@@ -15107,6 +15112,7 @@ unreserved_keyword:
 			| BACKWARD
 			| BEFORE
 			| BEGIN_P
+			| BITWISE
 			| BY
 			| CACHE
 			| CALL
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..6a13d19 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12833,6 +12833,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	int			i_opcintype;
 	int			i_opckeytype;
 	int			i_opcdefault;
+	int			i_opcisbitwise;
 	int			i_opcfamily;
 	int			i_opcfamilyname;
 	int			i_opcfamilynsp;
@@ -12849,6 +12850,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	char	   *opcintype;
 	char	   *opckeytype;
 	char	   *opcdefault;
+	char	   *opcisbitwise;
 	char	   *opcfamily;
 	char	   *opcfamilyname;
 	char	   *opcfamilynsp;
@@ -12875,11 +12877,25 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	nameusing = createPQExpBuffer();
 
 	/* Get additional fields from the pg_opclass row */
-	if (fout->remoteVersion >= 80300)
+	if (fout->remoteVersion >= 13000)
+	{
+		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
+						  "opckeytype::pg_catalog.regtype, "
+						  "opcdefault, opcisbitwise, opcfamily, "
+						  "opfname AS opcfamilyname, "
+						  "nspname AS opcfamilynsp, "
+						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
+						  "FROM pg_catalog.pg_opclass c "
+						  "LEFT JOIN pg_catalog.pg_opfamily f ON f.oid = opcfamily "
+						  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = opfnamespace "
+						  "WHERE c.oid = '%u'::pg_catalog.oid",
+						  opcinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 80300)
 	{
 		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
 						  "opckeytype::pg_catalog.regtype, "
-						  "opcdefault, opcfamily, "
+						  "opcdefault, 'f' as opcisbitwise, opcfamily, "
 						  "opfname AS opcfamilyname, "
 						  "nspname AS opcfamilynsp, "
 						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
@@ -12893,7 +12909,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	{
 		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
 						  "opckeytype::pg_catalog.regtype, "
-						  "opcdefault, NULL AS opcfamily, "
+						  "opcdefault, 'f' as opcisbitwise, NULL AS opcfamily, "
 						  "NULL AS opcfamilyname, "
 						  "NULL AS opcfamilynsp, "
 						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcamid) AS amname "
@@ -12907,6 +12923,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	i_opcintype = PQfnumber(res, "opcintype");
 	i_opckeytype = PQfnumber(res, "opckeytype");
 	i_opcdefault = PQfnumber(res, "opcdefault");
+	i_opcisbitwise = PQfnumber(res, "opcisbitwise");
 	i_opcfamily = PQfnumber(res, "opcfamily");
 	i_opcfamilyname = PQfnumber(res, "opcfamilyname");
 	i_opcfamilynsp = PQfnumber(res, "opcfamilynsp");
@@ -12916,6 +12933,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	opcintype = pg_strdup(PQgetvalue(res, 0, i_opcintype));
 	opckeytype = PQgetvalue(res, 0, i_opckeytype);
 	opcdefault = PQgetvalue(res, 0, i_opcdefault);
+	opcisbitwise = PQgetvalue(res, 0, i_opcisbitwise);
 	/* opcfamily will still be needed after we PQclear res */
 	opcfamily = pg_strdup(PQgetvalue(res, 0, i_opcfamily));
 	opcfamilyname = PQgetvalue(res, 0, i_opcfamilyname);
@@ -12933,6 +12951,8 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 					  fmtQualifiedDumpable(opcinfo));
 	if (strcmp(opcdefault, "t") == 0)
 		appendPQExpBufferStr(q, "DEFAULT ");
+	if (strcmp(opcisbitwise, "t") == 0)
+		appendPQExpBufferStr(q, "BITWISE ");
 	appendPQExpBuffer(q, "FOR TYPE %s USING %s",
 					  opcintype,
 					  fmtId(amname));
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 2d57510..045d9b6 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -17,76 +17,76 @@
 # assigned on-the-fly during initdb.
 
 { opcmethod => 'btree', opcname => 'array_ops', opcfamily => 'btree/array_ops',
-  opcintype => 'anyarray' },
+  opcintype => 'anyarray', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'array_ops', opcfamily => 'hash/array_ops',
-  opcintype => 'anyarray' },
+  opcintype => 'anyarray', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bit_ops', opcfamily => 'btree/bit_ops',
-  opcintype => 'bit' },
+  opcintype => 'bit', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bool_ops', opcfamily => 'btree/bool_ops',
-  opcintype => 'bool' },
+  opcintype => 'bool', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bpchar_ops',
-  opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar' },
+  opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bpchar_ops', opcfamily => 'hash/bpchar_ops',
-  opcintype => 'bpchar' },
+  opcintype => 'bpchar', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'bytea_ops', opcfamily => 'btree/bytea_ops',
-  opcintype => 'bytea' },
+  opcintype => 'bytea', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'char_ops', opcfamily => 'btree/char_ops',
-  opcintype => 'char' },
+  opcintype => 'char', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'char_ops', opcfamily => 'hash/char_ops',
-  opcintype => 'char' },
+  opcintype => 'char', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'cidr_ops', opcfamily => 'btree/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'cidr_ops', opcfamily => 'hash/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't'},
 { oid => '3122', oid_symbol => 'DATE_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'date_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'date' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'date', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'date_ops', opcfamily => 'hash/date_ops',
-  opcintype => 'date' },
+  opcintype => 'date', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'float4_ops', opcfamily => 'btree/float_ops',
-  opcintype => 'float4' },
+  opcintype => 'float4', opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'float4_ops', opcfamily => 'hash/float_ops',
-  opcintype => 'float4' },
+  opcintype => 'float4', opcisbitwise => 'f'},
 { oid => '3123', oid_symbol => 'FLOAT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'float8_ops', opcfamily => 'btree/float_ops',
-  opcintype => 'float8' },
+  opcintype => 'float8', opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'float8_ops', opcfamily => 'hash/float_ops',
-  opcintype => 'float8' },
+  opcintype => 'float8', opcisbitwise => 'f'},
 { opcmethod => 'btree', opcname => 'inet_ops', opcfamily => 'btree/network_ops',
-  opcintype => 'inet' },
+  opcintype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'inet_ops', opcfamily => 'hash/network_ops',
-  opcintype => 'inet' },
+  opcintype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'inet_ops', opcfamily => 'gist/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'inet_ops',
-  opcfamily => 'spgist/network_ops', opcintype => 'inet' },
+  opcfamily => 'spgist/network_ops', opcintype => 'inet', opcisbitwise => 't'},
 { oid => '1979', oid_symbol => 'INT2_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int2_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int2' },
+  opcintype => 'int2', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'int2_ops', opcfamily => 'hash/integer_ops',
-  opcintype => 'int2' },
+  opcintype => 'int2', opcisbitwise => 't'},
 { oid => '1978', oid_symbol => 'INT4_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int4_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int4' },
+  opcintype => 'int4', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'int4_ops', opcfamily => 'hash/integer_ops',
-  opcintype => 'int4' },
+  opcintype => 'int4', opcisbitwise => 't'},
 { oid => '3124', oid_symbol => 'INT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int8_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int8' },
+  opcintype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'int8_ops', opcfamily => 'hash/integer_ops',
-  opcintype => 'int8' },
+  opcintype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'interval_ops',
-  opcfamily => 'btree/interval_ops', opcintype => 'interval' },
+  opcfamily => 'btree/interval_ops', opcintype => 'interval', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'interval_ops',
-  opcfamily => 'hash/interval_ops', opcintype => 'interval' },
+  opcfamily => 'hash/interval_ops', opcintype => 'interval', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'macaddr_ops',
-  opcfamily => 'btree/macaddr_ops', opcintype => 'macaddr' },
+  opcfamily => 'btree/macaddr_ops', opcintype => 'macaddr', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'macaddr_ops',
-  opcfamily => 'hash/macaddr_ops', opcintype => 'macaddr' },
+  opcfamily => 'hash/macaddr_ops', opcintype => 'macaddr', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'macaddr8_ops',
-  opcfamily => 'btree/macaddr8_ops', opcintype => 'macaddr8' },
+  opcfamily => 'btree/macaddr8_ops', opcintype => 'macaddr8', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'macaddr8_ops',
-  opcfamily => 'hash/macaddr8_ops', opcintype => 'macaddr8' },
+  opcfamily => 'hash/macaddr8_ops', opcintype => 'macaddr8', opcisbitwise => 't'},
 
 # Here's an ugly little hack to save space in the system catalog indexes.
 # btree doesn't ordinarily allow a storage type different from input type;
@@ -94,157 +94,159 @@
 # and we can safely omit that within an index entry.  So we declare the
 # btree opclass for name as using cstring storage type.
 { opcmethod => 'btree', opcname => 'name_ops', opcfamily => 'btree/text_ops',
-  opcintype => 'name', opckeytype => 'cstring' },
+  opcintype => 'name', opckeytype => 'cstring', opcisbitwise => 't'},
 
 { opcmethod => 'hash', opcname => 'name_ops', opcfamily => 'hash/text_ops',
-  opcintype => 'name' },
+  opcintype => 'name', opcisbitwise => 't'},
 { oid => '3125', oid_symbol => 'NUMERIC_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'numeric_ops',
-  opcfamily => 'btree/numeric_ops', opcintype => 'numeric' },
+  opcfamily => 'btree/numeric_ops', opcintype => 'numeric',
+  opcisbitwise => 'f'},
 { opcmethod => 'hash', opcname => 'numeric_ops',
-  opcfamily => 'hash/numeric_ops', opcintype => 'numeric' },
+  opcfamily => 'hash/numeric_ops', opcintype => 'numeric',
+  opcisbitwise => 'f'},
 { oid => '1981', oid_symbol => 'OID_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'oid_ops', opcfamily => 'btree/oid_ops',
-  opcintype => 'oid' },
+  opcintype => 'oid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'oid_ops', opcfamily => 'hash/oid_ops',
-  opcintype => 'oid' },
+  opcintype => 'oid', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'oidvector_ops',
-  opcfamily => 'btree/oidvector_ops', opcintype => 'oidvector' },
+  opcfamily => 'btree/oidvector_ops', opcintype => 'oidvector', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'oidvector_ops',
-  opcfamily => 'hash/oidvector_ops', opcintype => 'oidvector' },
+  opcfamily => 'hash/oidvector_ops', opcintype => 'oidvector', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'record_ops',
-  opcfamily => 'btree/record_ops', opcintype => 'record' },
+  opcfamily => 'btree/record_ops', opcintype => 'record', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'record_image_ops',
   opcfamily => 'btree/record_image_ops', opcintype => 'record',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { oid => '3126', oid_symbol => 'TEXT_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'text_ops', opcfamily => 'btree/text_ops',
-  opcintype => 'text' },
+  opcintype => 'text', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'text_ops', opcfamily => 'hash/text_ops',
-  opcintype => 'text' },
+  opcintype => 'text', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'time_ops', opcfamily => 'btree/time_ops',
-  opcintype => 'time' },
+  opcintype => 'time', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'time_ops', opcfamily => 'hash/time_ops',
-  opcintype => 'time' },
+  opcintype => 'time', opcisbitwise => 't'},
 { oid => '3127', oid_symbol => 'TIMESTAMPTZ_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'timestamptz_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'timestamptz' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'timestamptz', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'timestamptz_ops',
-  opcfamily => 'hash/timestamptz_ops', opcintype => 'timestamptz' },
+  opcfamily => 'hash/timestamptz_ops', opcintype => 'timestamptz', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'timetz_ops',
-  opcfamily => 'btree/timetz_ops', opcintype => 'timetz' },
+  opcfamily => 'btree/timetz_ops', opcintype => 'timetz', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'timetz_ops', opcfamily => 'hash/timetz_ops',
-  opcintype => 'timetz' },
+  opcintype => 'timetz', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'varbit_ops',
-  opcfamily => 'btree/varbit_ops', opcintype => 'varbit' },
+  opcfamily => 'btree/varbit_ops', opcintype => 'varbit', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'varchar_ops', opcfamily => 'btree/text_ops',
-  opcintype => 'text', opcdefault => 'f' },
+  opcintype => 'text', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'varchar_ops', opcfamily => 'hash/text_ops',
-  opcintype => 'text', opcdefault => 'f' },
+  opcintype => 'text', opcdefault => 'f', opcisbitwise => 't'},
 { oid => '3128', oid_symbol => 'TIMESTAMP_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'timestamp_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'timestamp' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'timestamp', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'timestamp_ops',
-  opcfamily => 'hash/timestamp_ops', opcintype => 'timestamp' },
+  opcfamily => 'hash/timestamp_ops', opcintype => 'timestamp', opcisbitwise => 't'},
 { oid => '4217', oid_symbol => 'TEXT_BTREE_PATTERN_OPS_OID',
   opcmethod => 'btree', opcname => 'text_pattern_ops',
   opcfamily => 'btree/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { oid => '4218', oid_symbol => 'VARCHAR_BTREE_PATTERN_OPS_OID',
   opcmethod => 'btree', opcname => 'varchar_pattern_ops',
   opcfamily => 'btree/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { oid => '4219', oid_symbol => 'BPCHAR_BTREE_PATTERN_OPS_OID',
   opcmethod => 'btree', opcname => 'bpchar_pattern_ops',
   opcfamily => 'btree/bpchar_pattern_ops', opcintype => 'bpchar',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'money_ops', opcfamily => 'btree/money_ops',
-  opcintype => 'money' },
+  opcintype => 'money', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bool_ops', opcfamily => 'hash/bool_ops',
-  opcintype => 'bool' },
+  opcintype => 'bool', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bytea_ops', opcfamily => 'hash/bytea_ops',
-  opcintype => 'bytea' },
+  opcintype => 'bytea', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'tid_ops', opcfamily => 'btree/tid_ops',
-  opcintype => 'tid' },
+  opcintype => 'tid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'xid_ops', opcfamily => 'hash/xid_ops',
-  opcintype => 'xid' },
+  opcintype => 'xid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'cid_ops', opcfamily => 'hash/cid_ops',
-  opcintype => 'cid' },
+  opcintype => 'cid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'tid_ops', opcfamily => 'hash/tid_ops',
-  opcintype => 'tid' },
+  opcintype => 'tid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'text_pattern_ops',
   opcfamily => 'hash/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'varchar_pattern_ops',
   opcfamily => 'hash/text_pattern_ops', opcintype => 'text',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'bpchar_pattern_ops',
   opcfamily => 'hash/bpchar_pattern_ops', opcintype => 'bpchar',
-  opcdefault => 'f' },
+  opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'aclitem_ops',
-  opcfamily => 'hash/aclitem_ops', opcintype => 'aclitem' },
+  opcfamily => 'hash/aclitem_ops', opcintype => 'aclitem', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'box_ops', opcfamily => 'gist/box_ops',
-  opcintype => 'box' },
+  opcintype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'point_ops', opcfamily => 'gist/point_ops',
-  opcintype => 'point', opckeytype => 'box' },
+  opcintype => 'point', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'poly_ops', opcfamily => 'gist/poly_ops',
-  opcintype => 'polygon', opckeytype => 'box' },
+  opcintype => 'polygon', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'circle_ops', opcfamily => 'gist/circle_ops',
-  opcintype => 'circle', opckeytype => 'box' },
+  opcintype => 'circle', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'array_ops', opcfamily => 'gin/array_ops',
-  opcintype => 'anyarray', opckeytype => 'anyelement' },
+  opcintype => 'anyarray', opckeytype => 'anyelement', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'uuid_ops', opcfamily => 'btree/uuid_ops',
-  opcintype => 'uuid' },
+  opcintype => 'uuid', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'uuid_ops', opcfamily => 'hash/uuid_ops',
-  opcintype => 'uuid' },
+  opcintype => 'uuid', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'pg_lsn_ops',
-  opcfamily => 'btree/pg_lsn_ops', opcintype => 'pg_lsn' },
+  opcfamily => 'btree/pg_lsn_ops', opcintype => 'pg_lsn', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'pg_lsn_ops', opcfamily => 'hash/pg_lsn_ops',
-  opcintype => 'pg_lsn' },
+  opcintype => 'pg_lsn', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'enum_ops', opcfamily => 'btree/enum_ops',
-  opcintype => 'anyenum' },
+  opcintype => 'anyenum', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'enum_ops', opcfamily => 'hash/enum_ops',
-  opcintype => 'anyenum' },
+  opcintype => 'anyenum', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'tsvector_ops',
-  opcfamily => 'btree/tsvector_ops', opcintype => 'tsvector' },
+  opcfamily => 'btree/tsvector_ops', opcintype => 'tsvector', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'tsvector_ops',
   opcfamily => 'gist/tsvector_ops', opcintype => 'tsvector',
-  opckeytype => 'gtsvector' },
+  opckeytype => 'gtsvector', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'tsvector_ops',
   opcfamily => 'gin/tsvector_ops', opcintype => 'tsvector',
-  opckeytype => 'text' },
+  opckeytype => 'text', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'tsquery_ops',
-  opcfamily => 'btree/tsquery_ops', opcintype => 'tsquery' },
+  opcfamily => 'btree/tsquery_ops', opcintype => 'tsquery', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'tsquery_ops',
   opcfamily => 'gist/tsquery_ops', opcintype => 'tsquery',
-  opckeytype => 'int8' },
+  opckeytype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'range_ops', opcfamily => 'btree/range_ops',
-  opcintype => 'anyrange' },
+  opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'range_ops', opcfamily => 'hash/range_ops',
-  opcintype => 'anyrange' },
+  opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'gist', opcname => 'range_ops', opcfamily => 'gist/range_ops',
-  opcintype => 'anyrange' },
+  opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'range_ops',
-  opcfamily => 'spgist/range_ops', opcintype => 'anyrange' },
+  opcfamily => 'spgist/range_ops', opcintype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'box_ops', opcfamily => 'spgist/box_ops',
-  opcintype => 'box' },
+  opcintype => 'box', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'quad_point_ops',
-  opcfamily => 'spgist/quad_point_ops', opcintype => 'point' },
+  opcfamily => 'spgist/quad_point_ops', opcintype => 'point', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'kd_point_ops',
-  opcfamily => 'spgist/kd_point_ops', opcintype => 'point', opcdefault => 'f' },
+  opcfamily => 'spgist/kd_point_ops', opcintype => 'point', opcdefault => 'f', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'text_ops', opcfamily => 'spgist/text_ops',
-  opcintype => 'text' },
+  opcintype => 'text', opcisbitwise => 't'},
 { opcmethod => 'spgist', opcname => 'poly_ops', opcfamily => 'spgist/poly_ops',
-  opcintype => 'polygon', opckeytype => 'box' },
+  opcintype => 'polygon', opckeytype => 'box', opcisbitwise => 't'},
 { opcmethod => 'btree', opcname => 'jsonb_ops', opcfamily => 'btree/jsonb_ops',
-  opcintype => 'jsonb' },
+  opcintype => 'jsonb', opcisbitwise => 't'},
 { opcmethod => 'hash', opcname => 'jsonb_ops', opcfamily => 'hash/jsonb_ops',
-  opcintype => 'jsonb' },
+  opcintype => 'jsonb', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'jsonb_ops', opcfamily => 'gin/jsonb_ops',
-  opcintype => 'jsonb', opckeytype => 'text' },
+  opcintype => 'jsonb', opckeytype => 'text', opcisbitwise => 't'},
 { opcmethod => 'gin', opcname => 'jsonb_path_ops',
   opcfamily => 'gin/jsonb_path_ops', opcintype => 'jsonb', opcdefault => 'f',
-  opckeytype => 'int4' },
+  opckeytype => 'int4', opcisbitwise => 't'},
 
 # BRIN operator classes
 
@@ -252,94 +254,94 @@
 
 { opcmethod => 'brin', opcname => 'bytea_minmax_ops',
   opcfamily => 'brin/bytea_minmax_ops', opcintype => 'bytea',
-  opckeytype => 'bytea' },
+  opckeytype => 'bytea', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'char_minmax_ops',
   opcfamily => 'brin/char_minmax_ops', opcintype => 'char',
-  opckeytype => 'char' },
+  opckeytype => 'char', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'name_minmax_ops',
   opcfamily => 'brin/name_minmax_ops', opcintype => 'name',
-  opckeytype => 'name' },
+  opckeytype => 'name', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'int8_minmax_ops',
   opcfamily => 'brin/integer_minmax_ops', opcintype => 'int8',
-  opckeytype => 'int8' },
+  opckeytype => 'int8', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'int2_minmax_ops',
   opcfamily => 'brin/integer_minmax_ops', opcintype => 'int2',
-  opckeytype => 'int2' },
+  opckeytype => 'int2', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'int4_minmax_ops',
   opcfamily => 'brin/integer_minmax_ops', opcintype => 'int4',
-  opckeytype => 'int4' },
+  opckeytype => 'int4', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'text_minmax_ops',
   opcfamily => 'brin/text_minmax_ops', opcintype => 'text',
-  opckeytype => 'text' },
+  opckeytype => 'text', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'oid_minmax_ops',
-  opcfamily => 'brin/oid_minmax_ops', opcintype => 'oid', opckeytype => 'oid' },
+  opcfamily => 'brin/oid_minmax_ops', opcintype => 'oid', opckeytype => 'oid', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'tid_minmax_ops',
-  opcfamily => 'brin/tid_minmax_ops', opcintype => 'tid', opckeytype => 'tid' },
+  opcfamily => 'brin/tid_minmax_ops', opcintype => 'tid', opckeytype => 'tid', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'float4_minmax_ops',
   opcfamily => 'brin/float_minmax_ops', opcintype => 'float4',
-  opckeytype => 'float4' },
+  opckeytype => 'float4', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'float8_minmax_ops',
   opcfamily => 'brin/float_minmax_ops', opcintype => 'float8',
-  opckeytype => 'float8' },
+  opckeytype => 'float8', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'macaddr_minmax_ops',
   opcfamily => 'brin/macaddr_minmax_ops', opcintype => 'macaddr',
-  opckeytype => 'macaddr' },
+  opckeytype => 'macaddr', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'macaddr8_minmax_ops',
   opcfamily => 'brin/macaddr8_minmax_ops', opcintype => 'macaddr8',
-  opckeytype => 'macaddr8' },
+  opckeytype => 'macaddr8', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'inet_minmax_ops',
   opcfamily => 'brin/network_minmax_ops', opcintype => 'inet',
-  opcdefault => 'f', opckeytype => 'inet' },
+  opcdefault => 'f', opckeytype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'inet_inclusion_ops',
   opcfamily => 'brin/network_inclusion_ops', opcintype => 'inet',
-  opckeytype => 'inet' },
+  opckeytype => 'inet', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'bpchar_minmax_ops',
   opcfamily => 'brin/bpchar_minmax_ops', opcintype => 'bpchar',
-  opckeytype => 'bpchar' },
+  opckeytype => 'bpchar', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'time_minmax_ops',
   opcfamily => 'brin/time_minmax_ops', opcintype => 'time',
-  opckeytype => 'time' },
+  opckeytype => 'time', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'date_minmax_ops',
   opcfamily => 'brin/datetime_minmax_ops', opcintype => 'date',
-  opckeytype => 'date' },
+  opckeytype => 'date', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'timestamp_minmax_ops',
   opcfamily => 'brin/datetime_minmax_ops', opcintype => 'timestamp',
-  opckeytype => 'timestamp' },
+  opckeytype => 'timestamp', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'timestamptz_minmax_ops',
   opcfamily => 'brin/datetime_minmax_ops', opcintype => 'timestamptz',
-  opckeytype => 'timestamptz' },
+  opckeytype => 'timestamptz', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'interval_minmax_ops',
   opcfamily => 'brin/interval_minmax_ops', opcintype => 'interval',
-  opckeytype => 'interval' },
+  opckeytype => 'interval', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'timetz_minmax_ops',
   opcfamily => 'brin/timetz_minmax_ops', opcintype => 'timetz',
-  opckeytype => 'timetz' },
+  opckeytype => 'timetz', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'bit_minmax_ops',
-  opcfamily => 'brin/bit_minmax_ops', opcintype => 'bit', opckeytype => 'bit' },
+  opcfamily => 'brin/bit_minmax_ops', opcintype => 'bit', opckeytype => 'bit', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'varbit_minmax_ops',
   opcfamily => 'brin/varbit_minmax_ops', opcintype => 'varbit',
-  opckeytype => 'varbit' },
+  opckeytype => 'varbit', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'numeric_minmax_ops',
   opcfamily => 'brin/numeric_minmax_ops', opcintype => 'numeric',
-  opckeytype => 'numeric' },
+  opckeytype => 'numeric', opcisbitwise => 't'},
 
 # no brin opclass for record, anyarray
 
 { opcmethod => 'brin', opcname => 'uuid_minmax_ops',
   opcfamily => 'brin/uuid_minmax_ops', opcintype => 'uuid',
-  opckeytype => 'uuid' },
+  opckeytype => 'uuid', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'range_inclusion_ops',
   opcfamily => 'brin/range_inclusion_ops', opcintype => 'anyrange',
-  opckeytype => 'anyrange' },
+  opckeytype => 'anyrange', opcisbitwise => 't'},
 { opcmethod => 'brin', opcname => 'pg_lsn_minmax_ops',
   opcfamily => 'brin/pg_lsn_minmax_ops', opcintype => 'pg_lsn',
-  opckeytype => 'pg_lsn' },
+  opckeytype => 'pg_lsn', opcisbitwise => 't'},
 
 # no brin opclass for enum, tsvector, tsquery, jsonb
 
 { opcmethod => 'brin', opcname => 'box_inclusion_ops',
   opcfamily => 'brin/box_inclusion_ops', opcintype => 'box',
-  opckeytype => 'box' },
+  opckeytype => 'box', opcisbitwise => 't'},
 
 # no brin opclass for the geometric types except box
 
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 84853c1..9ab32b3 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -73,6 +73,9 @@ CATALOG(pg_opclass,2616,OperatorClassRelationId)
 
 	/* type of data in index, or InvalidOid */
 	Oid			opckeytype BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
+
+	/* T if opclass equality also means "bitwise equality" */
+	bool		opcisbitwise BKI_DEFAULT(f);
 } FormData_pg_opclass;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff626cb..47ad85f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2577,6 +2577,7 @@ typedef struct CreateOpClassStmt
 	TypeName   *datatype;		/* datatype of indexed column */
 	List	   *items;			/* List of CreateOpClassItem nodes */
 	bool		isDefault;		/* Should be marked as default for type? */
+	bool		isBitwise;		/* Is opclass equality bitwise? */
 } CreateOpClassStmt;
 
 #define OPCLASS_ITEM_OPERATOR		1
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace84..d6a8e8f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -58,6 +58,7 @@ PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
 PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD)
 PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD)
+PG_KEYWORD("bitwise", BITWISE, UNRESERVED_KEYWORD)
 PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD)
 PG_KEYWORD("both", BOTH, RESERVED_KEYWORD)
 PG_KEYWORD("by", BY, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/create_am.out b/src/test/regress/expected/create_am.out
index 84da403..241b61c 100644
--- a/src/test/regress/expected/create_am.out
+++ b/src/test/regress/expected/create_am.out
@@ -13,7 +13,7 @@ CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base);
 ERROR:  data type box has no default operator class for access method "gist2"
 HINT:  You must specify an operator class for the index or define a default operator class for the data type.
 -- Make operator class for boxes using gist2
-CREATE OPERATOR CLASS box_ops DEFAULT
+CREATE OPERATOR CLASS box_ops DEFAULT BITWISE
 	FOR TYPE box USING gist2 AS
 	OPERATOR 1	<<,
 	OPERATOR 2	&<,
diff --git a/src/test/regress/sql/create_am.sql b/src/test/regress/sql/create_am.sql
index a7f6de7..46b5ca0 100644
--- a/src/test/regress/sql/create_am.sql
+++ b/src/test/regress/sql/create_am.sql
@@ -14,7 +14,7 @@ CREATE ACCESS METHOD bogus TYPE INDEX HANDLER heap_tableam_handler;
 CREATE INDEX grect2ind2 ON fast_emp4000 USING gist2 (home_base);
 
 -- Make operator class for boxes using gist2
-CREATE OPERATOR CLASS box_ops DEFAULT
+CREATE OPERATOR CLASS box_ops DEFAULT BITWISE
 	FOR TYPE box USING gist2 AS
 	OPERATOR 1	<<,
 	OPERATOR 2	&<,
#15Antonin Houska
ah@cybertec.at
In reply to: Anastasia Lubennikova (#14)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Anastasia Lubennikova <a.lubennikova@postgrespro.ru> wrote:

I attached new version with pg_opclass documentation update.

One more thing I am uncertain about  is array_ops. Arrays may contain bitwise
and not bitwise element types.
What is the correct value of opcisbitwise the array_ops itself?

How about setting opcisbitwise to false for the array_ops opclass and checking
opcisbitwise of the element type whenever we need to know whether the array is
"bitwise equal"? When checking array_eq(), I thought whether the existence of
"expanded array" format is a problem but it does not seem to be: the
conversion of "expanded" value to "flat" value and then back to the "expanded"
should not change the array contents.

Anyway, in the current version of the patch I see that array_ops opclasses
have opcisbitwise=true. It should be false even if you don't use the approach
of checking the element type.

Besides that, I think that record_ops is similar to array_ops and therefore it
should not set opcisbitwise to true.

I also remember that, when thinking about the problem in the context of the
aggregate push down patch, I considered some of the geometric types
problematic. For example, box_eq() uses this expression

#define FPeq(A,B) (fabs((A) - (B)) <= EPSILON)

so equality does not imply bitwise equality here. Maybe you should only set
the flag for btree opclasses for now.

--
Antonin Houska
Web: https://www.cybertec-postgresql.com

#16Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#13)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Wed, Nov 13, 2019 at 4:25 PM Peter Geoghegan <pg@bowt.ie> wrote:

I think that that's probably not desirable. There should at least be a
strong practical advantage if we go that way. This would mean ALTER
OPERATOR CLASS could change the "substance" of an opclass, which is
fundamentally different from what it can do already (it currently just
changes the owner, or the schema that it is stored in).

My impression is that this is more of an implementation restriction
than a design goal. I don't really remember the details, but it seems
to me that there were locking and/or cache invalidation problems with
making ALTER OPERATOR CLASS do more substantive things -- and that it
was because of those problems, not a lack of desire, that we didn't
support it.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

In reply to: Robert Haas (#16)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Thu, Dec 19, 2019 at 12:05 PM Robert Haas <robertmhaas@gmail.com> wrote:

My impression is that this is more of an implementation restriction
than a design goal. I don't really remember the details, but it seems
to me that there were locking and/or cache invalidation problems with
making ALTER OPERATOR CLASS do more substantive things -- and that it
was because of those problems, not a lack of desire, that we didn't
support it.

I agree with you. My point was only that this is something that the
operator class author is really expected to get right the first time
around -- just like the behavior of B-Tree support function 1. We're
really only concerned about the upgrade path for external types that
could see a benefit from the optimization planned for nbtree (and
possibly other such optimization). Providing a non-disruptive way to
get that benefit after a pg_upgrade only seems like a nice-to-have to
me, because it's not as if anything will stop working as well as it
once did. Also, there aren't that many external types that will be
made more useful by being able to use optimizations like
deduplication; in practice almost all B-Tree indexes only use a small
handful of operator classes that are shipped in core Postgres. Once
you're using common types like text and integer, a pg_upgrade'd
database is only a REINDEX away from being able to use deduplication
(though I am not even sure if even that will be necessary in the final
patch; I hope to be able to avoid even that inconvenience with indexes
using core operator classes).

If the underlying behavior of an operator class actually changes, then
that's a disaster for all the usual reasons. It doesn't make that much
sense to reverse an earlier decision to make an operator class
BITWISE. Better to drop everything, and recreate everything, since
your indexes should be considered corrupt anyway. (Also, I don't think
that it's that hard to get it right, so this will probably never
happen.)

--
Peter Geoghegan

#18Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Antonin Houska (#15)
3 attachment(s)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

19.12.2019 18:19, Antonin Houska wrote:

Anastasia Lubennikova <a.lubennikova@postgrespro.ru> wrote:

I attached new version with pg_opclass documentation update.

One more thing I am uncertain about  is array_ops. Arrays may contain bitwise
and not bitwise element types.
What is the correct value of opcisbitwise the array_ops itself?

How about setting opcisbitwise to false for the array_ops opclass and checking
opcisbitwise of the element type whenever we need to know whether the array is
"bitwise equal"? When checking array_eq(), I thought whether the existence of
"expanded array" format is a problem but it does not seem to be: the
conversion of "expanded" value to "flat" value and then back to the "expanded"
should not change the array contents.

Anyway, in the current version of the patch I see that array_ops opclasses
have opcisbitwise=true. It should be false even if you don't use the approach
of checking the element type.

Besides that, I think that record_ops is similar to array_ops and therefore it
should not set opcisbitwise to true.

I also remember that, when thinking about the problem in the context of the
aggregate push down patch, I considered some of the geometric types
problematic. For example, box_eq() uses this expression

#define FPeq(A,B) (fabs((A) - (B)) <= EPSILON)

so equality does not imply bitwise equality here. Maybe you should only set
the flag for btree opclasses for now.

Thank you for pointing out at the issue with geometric opclasses.
If I understand it correctly, regular float types are not bitwise as well.

I updated the patchset.
The first patch now contains only infrastructure changes
and the second one sets opcisbitwise for btree opclasses in pg_opclass.dat.

I've tried to be conservative and only mark types that are 100% bitwise
safe.
See attached v2-Opclass-isbitwise.out file.

Non-atomic types, such as record, range, json and enum depend on element
types.
Text can be considered bitwise (i.e. texteq uses memcmp) only when
specific collation clauses are satisfied.

We can make this 'opcisbitwise' parameter enum (or char) instead of
boolean to mark
"always bitwise", "never bitwise" and "maybe bitwise". Though, I doubt
if it will be helpful in any real use case.

What do you think?

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

Attachments:

v4-Opclass-bitwise-equality-0001.patchtext/x-patch; name=v4-Opclass-bitwise-equality-0001.patchDownload
commit 891c03c4b8f37cf6711d6ae84f4e75573c123ec8
Author: Anastasia <a.lubennikova@postgrespro.ru>
Date:   Mon Dec 23 19:52:22 2019 +0300

    v4-Opclass-bitwise-equality-0001.patch
    Add new infrastracture to mark opclass as BITWISE. Actual values of this parameter will be added in the following patch.

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index c915885..9b5c33b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -3111,7 +3111,7 @@ create operator public.>^ (
 create operator family my_op_family using btree;
 create function my_op_cmp(a int, b int) returns int as
   $$begin return btint4cmp(a, b); end $$ language plpgsql;
-create operator class my_op_class for type int using btree family my_op_family as
+create operator class my_op_class bitwise for type int using btree family my_op_family as
  operator 1 public.<^,
  operator 3 public.=^,
  operator 5 public.>^,
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 4f29e7c..48263d6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -825,7 +825,7 @@ create operator family my_op_family using btree;
 create function my_op_cmp(a int, b int) returns int as
   $$begin return btint4cmp(a, b); end $$ language plpgsql;
 
-create operator class my_op_class for type int using btree family my_op_family as
+create operator class my_op_class bitwise for type int using btree family my_op_family as
  operator 1 public.<^,
  operator 3 public.=^,
  operator 5 public.>^,
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 55694c4..87cc344 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4544,6 +4544,13 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
       <entry>Type of data stored in index, or zero if same as <structfield>opcintype</structfield></entry>
      </row>
 
+     <row>
+      <entry><structfield>opcisbitwise</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if the operator class equality is the same as equivalence</entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
diff --git a/doc/src/sgml/ref/create_opclass.sgml b/doc/src/sgml/ref/create_opclass.sgml
index dd5252f..8d0ddfc 100644
--- a/doc/src/sgml/ref/create_opclass.sgml
+++ b/doc/src/sgml/ref/create_opclass.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
+CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAULT | BITWISE ] FOR TYPE <replaceable class="parameter">data_type</replaceable>
   USING <replaceable class="parameter">index_method</replaceable> [ FAMILY <replaceable class="parameter">family_name</replaceable> ] AS
   {  OPERATOR <replaceable class="parameter">strategy_number</replaceable> <replaceable class="parameter">operator_name</replaceable> [ ( <replaceable class="parameter">op_type</replaceable>, <replaceable class="parameter">op_type</replaceable> ) ] [ FOR SEARCH | FOR ORDER BY <replaceable class="parameter">sort_family_name</replaceable> ]
    | FUNCTION <replaceable class="parameter">support_number</replaceable> [ ( <replaceable class="parameter">op_type</replaceable> [ , <replaceable class="parameter">op_type</replaceable> ] ) ] <replaceable class="parameter">function_name</replaceable> ( <replaceable class="parameter">argument_type</replaceable> [, ...] )
@@ -106,6 +106,20 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
     </listitem>
    </varlistentry>
 
+    <varlistentry>
+    <term><literal>BITWISE</literal></term>
+    <listitem>
+     <para>
+      If present, the operator class equality is the same as equivalence.
+      For example, two integers that compare equal are also binary equal,
+      while, numerics can compare equal but have different scales,
+      thus numeric opclass is not <literal>BITWISE</literal>. Even though,
+      most opclasses implement bitwise equal comparison, this behaviour
+      must be set explicitly.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">data_type</replaceable></term>
     <listitem>
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index cb7a6bd..c0fe187 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -653,6 +653,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(typeoid);
 	values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(stmt->isDefault);
 	values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(storageoid);
+	values[Anum_pg_opclass_opcisbitwise - 1] = BoolGetDatum(stmt->isBitwise);
 
 	tup = heap_form_tuple(rel->rd_att, values, nulls);
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a9b8b84..73b076dd 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3790,6 +3790,7 @@ _copyCreateOpClassStmt(const CreateOpClassStmt *from)
 	COPY_NODE_FIELD(datatype);
 	COPY_NODE_FIELD(items);
 	COPY_SCALAR_FIELD(isDefault);
+	COPY_SCALAR_FIELD(isBitwise);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2fcd4a3..8f4ece4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1609,6 +1609,7 @@ _equalCreateOpClassStmt(const CreateOpClassStmt *a, const CreateOpClassStmt *b)
 	COMPARE_NODE_FIELD(datatype);
 	COMPARE_NODE_FIELD(items);
 	COMPARE_SCALAR_FIELD(isDefault);
+	COMPARE_SCALAR_FIELD(isBitwise);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c508684..683f9b4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -447,7 +447,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <boolean> opt_instead
 %type <boolean> opt_unique opt_concurrently opt_verbose opt_full
-%type <boolean> opt_freeze opt_analyze opt_default opt_recheck
+%type <boolean> opt_freeze opt_analyze opt_default opt_recheck opt_bitwise
 %type <defelt>	opt_binary copy_delimiter
 
 %type <boolean> copy_from opt_program
@@ -618,7 +618,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
 	ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
 
-	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
+	BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BITWISE
 	BOOLEAN_P BOTH BY
 
 	CACHE CALL CALLED CASCADE CASCADED CASE CAST CATALOG_P CHAIN CHAR_P
@@ -5953,16 +5953,17 @@ opt_if_not_exists: IF_P NOT EXISTS              { $$ = true; }
  *****************************************************************************/
 
 CreateOpClassStmt:
-			CREATE OPERATOR CLASS any_name opt_default FOR TYPE_P Typename
+			CREATE OPERATOR CLASS any_name opt_default opt_bitwise FOR TYPE_P Typename
 			USING access_method opt_opfamily AS opclass_item_list
 				{
 					CreateOpClassStmt *n = makeNode(CreateOpClassStmt);
 					n->opclassname = $4;
 					n->isDefault = $5;
-					n->datatype = $8;
-					n->amname = $10;
-					n->opfamilyname = $11;
-					n->items = $13;
+					n->isBitwise = $6;
+					n->datatype = $9;
+					n->amname = $11;
+					n->opfamilyname = $12;
+					n->items = $14;
 					$$ = (Node *) n;
 				}
 		;
@@ -6025,6 +6026,10 @@ opt_default:	DEFAULT						{ $$ = true; }
 			| /*EMPTY*/						{ $$ = false; }
 		;
 
+opt_bitwise:	BITWISE						{ $$ = true; }
+			| /*EMPTY*/						{ $$ = false; }
+		;
+
 opt_opfamily:	FAMILY any_name				{ $$ = $2; }
 			| /*EMPTY*/						{ $$ = NIL; }
 		;
@@ -15129,6 +15134,7 @@ unreserved_keyword:
 			| BACKWARD
 			| BEFORE
 			| BEGIN_P
+			| BITWISE
 			| BY
 			| CACHE
 			| CALL
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 08658c8..00e248b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12833,6 +12833,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	int			i_opcintype;
 	int			i_opckeytype;
 	int			i_opcdefault;
+	int			i_opcisbitwise;
 	int			i_opcfamily;
 	int			i_opcfamilyname;
 	int			i_opcfamilynsp;
@@ -12849,6 +12850,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	char	   *opcintype;
 	char	   *opckeytype;
 	char	   *opcdefault;
+	char	   *opcisbitwise;
 	char	   *opcfamily;
 	char	   *opcfamilyname;
 	char	   *opcfamilynsp;
@@ -12875,11 +12877,25 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	nameusing = createPQExpBuffer();
 
 	/* Get additional fields from the pg_opclass row */
-	if (fout->remoteVersion >= 80300)
+	if (fout->remoteVersion >= 13000)
+	{
+		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
+						  "opckeytype::pg_catalog.regtype, "
+						  "opcdefault, opcisbitwise, opcfamily, "
+						  "opfname AS opcfamilyname, "
+						  "nspname AS opcfamilynsp, "
+						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
+						  "FROM pg_catalog.pg_opclass c "
+						  "LEFT JOIN pg_catalog.pg_opfamily f ON f.oid = opcfamily "
+						  "LEFT JOIN pg_catalog.pg_namespace n ON n.oid = opfnamespace "
+						  "WHERE c.oid = '%u'::pg_catalog.oid",
+						  opcinfo->dobj.catId.oid);
+	}
+	else if (fout->remoteVersion >= 80300)
 	{
 		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
 						  "opckeytype::pg_catalog.regtype, "
-						  "opcdefault, opcfamily, "
+						  "opcdefault, 'f' as opcisbitwise, opcfamily, "
 						  "opfname AS opcfamilyname, "
 						  "nspname AS opcfamilynsp, "
 						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcmethod) AS amname "
@@ -12893,7 +12909,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	{
 		appendPQExpBuffer(query, "SELECT opcintype::pg_catalog.regtype, "
 						  "opckeytype::pg_catalog.regtype, "
-						  "opcdefault, NULL AS opcfamily, "
+						  "opcdefault, 'f' as opcisbitwise, NULL AS opcfamily, "
 						  "NULL AS opcfamilyname, "
 						  "NULL AS opcfamilynsp, "
 						  "(SELECT amname FROM pg_catalog.pg_am WHERE oid = opcamid) AS amname "
@@ -12907,6 +12923,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	i_opcintype = PQfnumber(res, "opcintype");
 	i_opckeytype = PQfnumber(res, "opckeytype");
 	i_opcdefault = PQfnumber(res, "opcdefault");
+	i_opcisbitwise = PQfnumber(res, "opcisbitwise");
 	i_opcfamily = PQfnumber(res, "opcfamily");
 	i_opcfamilyname = PQfnumber(res, "opcfamilyname");
 	i_opcfamilynsp = PQfnumber(res, "opcfamilynsp");
@@ -12916,6 +12933,7 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 	opcintype = pg_strdup(PQgetvalue(res, 0, i_opcintype));
 	opckeytype = PQgetvalue(res, 0, i_opckeytype);
 	opcdefault = PQgetvalue(res, 0, i_opcdefault);
+	opcisbitwise = PQgetvalue(res, 0, i_opcisbitwise);
 	/* opcfamily will still be needed after we PQclear res */
 	opcfamily = pg_strdup(PQgetvalue(res, 0, i_opcfamily));
 	opcfamilyname = PQgetvalue(res, 0, i_opcfamilyname);
@@ -12933,6 +12951,8 @@ dumpOpclass(Archive *fout, OpclassInfo *opcinfo)
 					  fmtQualifiedDumpable(opcinfo));
 	if (strcmp(opcdefault, "t") == 0)
 		appendPQExpBufferStr(q, "DEFAULT ");
+	if (strcmp(opcisbitwise, "t") == 0)
+		appendPQExpBufferStr(q, "BITWISE ");
 	appendPQExpBuffer(q, "FOR TYPE %s USING %s",
 					  opcintype,
 					  fmtId(amname));
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 84853c1..9ab32b3 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -73,6 +73,9 @@ CATALOG(pg_opclass,2616,OperatorClassRelationId)
 
 	/* type of data in index, or InvalidOid */
 	Oid			opckeytype BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
+
+	/* T if opclass equality also means "bitwise equality" */
+	bool		opcisbitwise BKI_DEFAULT(f);
 } FormData_pg_opclass;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff626cb..47ad85f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2577,6 +2577,7 @@ typedef struct CreateOpClassStmt
 	TypeName   *datatype;		/* datatype of indexed column */
 	List	   *items;			/* List of CreateOpClassItem nodes */
 	bool		isDefault;		/* Should be marked as default for type? */
+	bool		isBitwise;		/* Is opclass equality bitwise? */
 } CreateOpClassStmt;
 
 #define OPCLASS_ITEM_OPERATOR		1
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace84..d6a8e8f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -58,6 +58,7 @@ PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
 PG_KEYWORD("bigint", BIGINT, COL_NAME_KEYWORD)
 PG_KEYWORD("binary", BINARY, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("bit", BIT, COL_NAME_KEYWORD)
+PG_KEYWORD("bitwise", BITWISE, UNRESERVED_KEYWORD)
 PG_KEYWORD("boolean", BOOLEAN_P, COL_NAME_KEYWORD)
 PG_KEYWORD("both", BOTH, RESERVED_KEYWORD)
 PG_KEYWORD("by", BY, UNRESERVED_KEYWORD)
v4-Opclass-bitwise-equality-0002.patchtext/x-patch; name=v4-Opclass-bitwise-equality-0002.patchDownload
commit 4b0eb505204b1be0bb7ba1171e288881e14a9b7c
Author: Anastasia <a.lubennikova@postgrespro.ru>
Date:   Mon Dec 23 19:43:57 2019 +0300

    v4-Opclass-bitwise-equality-0002.patch
    Mark nbtree opclasses as bitwise in pg_opclass.dat

diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index 2d57510..72b563d 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -21,26 +21,26 @@
 { opcmethod => 'hash', opcname => 'array_ops', opcfamily => 'hash/array_ops',
   opcintype => 'anyarray' },
 { opcmethod => 'btree', opcname => 'bit_ops', opcfamily => 'btree/bit_ops',
-  opcintype => 'bit' },
+  opcintype => 'bit', opcisbitwise => 't' },
 { opcmethod => 'btree', opcname => 'bool_ops', opcfamily => 'btree/bool_ops',
-  opcintype => 'bool' },
+  opcintype => 'bool', opcisbitwise => 't' },
 { opcmethod => 'btree', opcname => 'bpchar_ops',
   opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar' },
 { opcmethod => 'hash', opcname => 'bpchar_ops', opcfamily => 'hash/bpchar_ops',
   opcintype => 'bpchar' },
 { opcmethod => 'btree', opcname => 'bytea_ops', opcfamily => 'btree/bytea_ops',
-  opcintype => 'bytea' },
+  opcintype => 'bytea', opcisbitwise => 't' },
 { opcmethod => 'btree', opcname => 'char_ops', opcfamily => 'btree/char_ops',
-  opcintype => 'char' },
+  opcintype => 'char', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'char_ops', opcfamily => 'hash/char_ops',
   opcintype => 'char' },
 { opcmethod => 'btree', opcname => 'cidr_ops', opcfamily => 'btree/network_ops',
-  opcintype => 'inet', opcdefault => 'f' },
+  opcintype => 'inet', opcdefault => 'f', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'cidr_ops', opcfamily => 'hash/network_ops',
   opcintype => 'inet', opcdefault => 'f' },
 { oid => '3122', oid_symbol => 'DATE_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'date_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'date' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'date', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'date_ops', opcfamily => 'hash/date_ops',
   opcintype => 'date' },
 { opcmethod => 'btree', opcname => 'float4_ops', opcfamily => 'btree/float_ops',
@@ -62,29 +62,29 @@
   opcfamily => 'spgist/network_ops', opcintype => 'inet' },
 { oid => '1979', oid_symbol => 'INT2_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int2_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int2' },
+  opcintype => 'int2', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'int2_ops', opcfamily => 'hash/integer_ops',
   opcintype => 'int2' },
 { oid => '1978', oid_symbol => 'INT4_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int4_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int4' },
+  opcintype => 'int4', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'int4_ops', opcfamily => 'hash/integer_ops',
   opcintype => 'int4' },
 { oid => '3124', oid_symbol => 'INT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int8_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int8' },
+  opcintype => 'int8', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'int8_ops', opcfamily => 'hash/integer_ops',
   opcintype => 'int8' },
 { opcmethod => 'btree', opcname => 'interval_ops',
-  opcfamily => 'btree/interval_ops', opcintype => 'interval' },
+  opcfamily => 'btree/interval_ops', opcintype => 'interval', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'interval_ops',
   opcfamily => 'hash/interval_ops', opcintype => 'interval' },
 { opcmethod => 'btree', opcname => 'macaddr_ops',
-  opcfamily => 'btree/macaddr_ops', opcintype => 'macaddr' },
+  opcfamily => 'btree/macaddr_ops', opcintype => 'macaddr', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'macaddr_ops',
   opcfamily => 'hash/macaddr_ops', opcintype => 'macaddr' },
 { opcmethod => 'btree', opcname => 'macaddr8_ops',
-  opcfamily => 'btree/macaddr8_ops', opcintype => 'macaddr8' },
+  opcfamily => 'btree/macaddr8_ops', opcintype => 'macaddr8', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'macaddr8_ops',
   opcfamily => 'hash/macaddr8_ops', opcintype => 'macaddr8' },
 
@@ -105,11 +105,11 @@
   opcfamily => 'hash/numeric_ops', opcintype => 'numeric' },
 { oid => '1981', oid_symbol => 'OID_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'oid_ops', opcfamily => 'btree/oid_ops',
-  opcintype => 'oid' },
+  opcintype => 'oid', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'oid_ops', opcfamily => 'hash/oid_ops',
   opcintype => 'oid' },
 { opcmethod => 'btree', opcname => 'oidvector_ops',
-  opcfamily => 'btree/oidvector_ops', opcintype => 'oidvector' },
+  opcfamily => 'btree/oidvector_ops', opcintype => 'oidvector', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'oidvector_ops',
   opcfamily => 'hash/oidvector_ops', opcintype => 'oidvector' },
 { opcmethod => 'btree', opcname => 'record_ops',
@@ -123,27 +123,27 @@
 { opcmethod => 'hash', opcname => 'text_ops', opcfamily => 'hash/text_ops',
   opcintype => 'text' },
 { opcmethod => 'btree', opcname => 'time_ops', opcfamily => 'btree/time_ops',
-  opcintype => 'time' },
+  opcintype => 'time', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'time_ops', opcfamily => 'hash/time_ops',
   opcintype => 'time' },
 { oid => '3127', oid_symbol => 'TIMESTAMPTZ_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'timestamptz_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'timestamptz' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'timestamptz', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'timestamptz_ops',
   opcfamily => 'hash/timestamptz_ops', opcintype => 'timestamptz' },
 { opcmethod => 'btree', opcname => 'timetz_ops',
-  opcfamily => 'btree/timetz_ops', opcintype => 'timetz' },
+  opcfamily => 'btree/timetz_ops', opcintype => 'timetz', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'timetz_ops', opcfamily => 'hash/timetz_ops',
   opcintype => 'timetz' },
 { opcmethod => 'btree', opcname => 'varbit_ops',
-  opcfamily => 'btree/varbit_ops', opcintype => 'varbit' },
+  opcfamily => 'btree/varbit_ops', opcintype => 'varbit', opcisbitwise => 't' },
 { opcmethod => 'btree', opcname => 'varchar_ops', opcfamily => 'btree/text_ops',
   opcintype => 'text', opcdefault => 'f' },
 { opcmethod => 'hash', opcname => 'varchar_ops', opcfamily => 'hash/text_ops',
   opcintype => 'text', opcdefault => 'f' },
 { oid => '3128', oid_symbol => 'TIMESTAMP_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'timestamp_ops',
-  opcfamily => 'btree/datetime_ops', opcintype => 'timestamp' },
+  opcfamily => 'btree/datetime_ops', opcintype => 'timestamp', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'timestamp_ops',
   opcfamily => 'hash/timestamp_ops', opcintype => 'timestamp' },
 { oid => '4217', oid_symbol => 'TEXT_BTREE_PATTERN_OPS_OID',
@@ -165,7 +165,7 @@
 { opcmethod => 'hash', opcname => 'bytea_ops', opcfamily => 'hash/bytea_ops',
   opcintype => 'bytea' },
 { opcmethod => 'btree', opcname => 'tid_ops', opcfamily => 'btree/tid_ops',
-  opcintype => 'tid' },
+  opcintype => 'tid', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'xid_ops', opcfamily => 'hash/xid_ops',
   opcintype => 'xid' },
 { opcmethod => 'hash', opcname => 'cid_ops', opcfamily => 'hash/cid_ops',
@@ -194,11 +194,11 @@
 { opcmethod => 'gin', opcname => 'array_ops', opcfamily => 'gin/array_ops',
   opcintype => 'anyarray', opckeytype => 'anyelement' },
 { opcmethod => 'btree', opcname => 'uuid_ops', opcfamily => 'btree/uuid_ops',
-  opcintype => 'uuid' },
+  opcintype => 'uuid', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'uuid_ops', opcfamily => 'hash/uuid_ops',
   opcintype => 'uuid' },
 { opcmethod => 'btree', opcname => 'pg_lsn_ops',
-  opcfamily => 'btree/pg_lsn_ops', opcintype => 'pg_lsn' },
+  opcfamily => 'btree/pg_lsn_ops', opcintype => 'pg_lsn', opcisbitwise => 't' },
 { opcmethod => 'hash', opcname => 'pg_lsn_ops', opcfamily => 'hash/pg_lsn_ops',
   opcintype => 'pg_lsn' },
 { opcmethod => 'btree', opcname => 'enum_ops', opcfamily => 'btree/enum_ops',
v4-Opclass-isbitwise.outtext/plain; charset=UTF-8; name=v4-Opclass-isbitwise.outDownload
#19Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Anastasia Lubennikova (#9)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

@@ -106,6 +106,18 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
</listitem>
</varlistentry>

+    <varlistentry>
+    <term><literal>NOT BITWISE</literal></term>
+    <listitem>
+     <para>
+      If present, the operator class equality is not the same as equivalence.
+      For example, two numerics can compare equal but have different scales.
+      Most opclasses implement bitwise equal comparison, alternative behaviour
+      must be set explicitly.
+     </para>
+    </listitem>
+   </varlistentry>

Am I the only one bothered by the fact that this patch (and all
downstream discussion) reduces the term "bitwise equality" to simply
"bitwise"? It reads really strange to me, both in the resulting SQL
grammar as well as in struct names, code comments etc. "This operator
class is bitwise."

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#19)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

Am I the only one bothered by the fact that this patch (and all
downstream discussion) reduces the term "bitwise equality" to simply
"bitwise"? It reads really strange to me, both in the resulting SQL
grammar as well as in struct names, code comments etc. "This operator
class is bitwise."

I agree, that's really poor English.

regards, tom lane

#21Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Alvaro Herrera (#19)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

24.12.2019 19:08, Alvaro Herrera wrote:

@@ -106,6 +106,18 @@ CREATE OPERATOR CLASS <replaceable class="parameter">name</replaceable> [ DEFAUL
</listitem>
</varlistentry>

+    <varlistentry>
+    <term><literal>NOT BITWISE</literal></term>
+    <listitem>
+     <para>
+      If present, the operator class equality is not the same as equivalence.
+      For example, two numerics can compare equal but have different scales.
+      Most opclasses implement bitwise equal comparison, alternative behaviour
+      must be set explicitly.
+     </para>
+    </listitem>
+   </varlistentry>

Am I the only one bothered by the fact that this patch (and all
downstream discussion) reduces the term "bitwise equality" to simply
"bitwise"? It reads really strange to me, both in the resulting SQL
grammar as well as in struct names, code comments etc. "This operator
class is bitwise."

Thank you for pointing that out.
Do you have any suggestions on how to name it better?
Should it rather be "CREATE OPERATOR CLASS ... BITWISE EQUAL" ?

In the recent version of the patch I also had a question,
if it will be useful to do this option enum instead of boolean:

We can make this 'opcisbitwise' parameter enum (or char) instead of
boolean to mark
"always bitwise", "never bitwise" and "maybe bitwise".

This decision will also affect the syntax. So I'd rather agree on that
before updating syntax.
Do you have an opinion on that?

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#22Robert Haas
robertmhaas@gmail.com
In reply to: Anastasia Lubennikova (#18)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Tue, Dec 24, 2019 at 7:29 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

We can make this 'opcisbitwise' parameter enum (or char) instead of
boolean to mark
"always bitwise", "never bitwise" and "maybe bitwise". Though, I doubt
if it will be helpful in any real use case.

What would be the difference between "never bitwise" and "maybe
bitwise" in that scheme?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#23Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Robert Haas (#22)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

29.12.2019 2:56, Robert Haas wrote:

On Tue, Dec 24, 2019 at 7:29 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

We can make this 'opcisbitwise' parameter enum (or char) instead of
boolean to mark
"always bitwise", "never bitwise" and "maybe bitwise". Though, I doubt
if it will be helpful in any real use case.

What would be the difference between "never bitwise" and "maybe
bitwise" in that scheme?

In this design "maybe" category reflects the need for an extra recheck.

For example, float and numeric types are "never bitwise equal", while array,
text, and other container types are "maybe bitwise equal". An array of
integers
or text with C collation can be treated as bitwise equal attributes, and it
would be too harsh to restrict them from deduplication.

What bothers me is that this option will unlikely be helpful on its own
and we
should also provide some kind of recheck function along with opclass, which
complicates this idea even further and doesn't seem very clear.

--
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

#24Robert Haas
robertmhaas@gmail.com
In reply to: Anastasia Lubennikova (#23)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Mon, Dec 30, 2019 at 10:57 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

In this design "maybe" category reflects the need for an extra recheck.

For example, float and numeric types are "never bitwise equal", while array,
text, and other container types are "maybe bitwise equal". An array of
integers
or text with C collation can be treated as bitwise equal attributes, and it
would be too harsh to restrict them from deduplication.

What bothers me is that this option will unlikely be helpful on its own
and we
should also provide some kind of recheck function along with opclass, which
complicates this idea even further and doesn't seem very clear.

It seems like the simplest thing might be to forget about the 'char'
column and just have a support function which can be used to assess
whether a given opclass's notion of equality is bitwise. If equality
is always bitwise, the function can always return true. If it's
sometimes bitwise, it can return true or false as appropriate. If it's
never bitwise, then it can either always return false or the support
function can be omitted altogether (so that the safe value is the
default).

I don't think you're going to save very much by avoiding an indirect
function call in the "always" case. It doesn't really seem worth the
complexity of making that a special case.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

In reply to: Robert Haas (#24)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Mon, Dec 30, 2019 at 9:45 AM Robert Haas <robertmhaas@gmail.com> wrote:

For example, float and numeric types are "never bitwise equal", while array,
text, and other container types are "maybe bitwise equal". An array of
integers
or text with C collation can be treated as bitwise equal attributes, and it
would be too harsh to restrict them from deduplication.

We might as well support container types (like array) in the first
Postgres version that has nbtree deduplication, I suppose. Even still,
I don't think that it actually matters much to users. B-Tree indexes
on arrays are probably very rare. Note that I don't consider text to
be a container type here -- obviously btree/text_ops is a very
important opclass for the deduplication feature. It may be the most
important opclass overall.

Recursively invoking a support function for the "contained" data type
in the btree/array_ops support function seems like it might be messy.
Not sure about that, though.

What bothers me is that this option will unlikely be helpful on its own
and we
should also provide some kind of recheck function along with opclass, which
complicates this idea even further and doesn't seem very clear.

It seems like the simplest thing might be to forget about the 'char'
column and just have a support function which can be used to assess
whether a given opclass's notion of equality is bitwise.

I like the idea of relying only on a support function.

This approach makes collations a problem that the opclass author has
to deal with directly, as is the case within a SortSupport support
function. Also seems like it would make life easier for third party
data types that want to make use of these optimizations (if in fact
there are any).

I also see little downside to this approach. The extra cycles
shouldn't be noticeable. As far as the B-Tree deduplication logic is
concerned, the final boolean value (is deduplication safe?) comes from
the index metapage -- we pass that down through an insertion scankey.
We only need to determine whether or not the optimization is safe at
CREATE INDEX time. (Actually, I don't want to commit to the idea that
nbtree should only call this support function at CREATE INDEX time
right now. I'm sure that it will hardly ever need to be called,
though.)

--
Peter Geoghegan

In reply to: Anastasia Lubennikova (#18)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Tue, Dec 24, 2019 at 4:29 AM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

I updated the patchset.
The first patch now contains only infrastructure changes
and the second one sets opcisbitwise for btree opclasses in pg_opclass.dat.

We should try to formally define what we're trying to represent about
B-Tree opclasses here -- the definition of
"opcisbitwise"/preciseness/whatever should be tightened up. In
particular, it should be clear how the "image" binary row comparators
[1]: https://www.postgresql.org/docs/devel/functions-comparisons.html#COMPOSITE-TYPE-COMPARISON
should be defined in terms of that existing concept -- we're talking
about exactly the same variety of "internal binary equality" here, I
think.

I propose that we adopt the following definition: For an operator
class to be safe, its equality operator has to always agree with
datum_image_eq() (i.e. two datums must be bitwise equal after
detoasting).

(Maybe we should say something about "operator *= equality" as well
(or instead), since that is already documented in [1]https://www.postgresql.org/docs/devel/functions-comparisons.html#COMPOSITE-TYPE-COMPARISON.)

We may also want to say something about foreign keys in this formal
definition of "opcisbitwise"/preciseness. Discussion around the bug
fixed by commit 1ffa59a85cb [1]https://www.postgresql.org/docs/devel/functions-comparisons.html#COMPOSITE-TYPE-COMPARISON showed that there was plenty of
confusion in this area. Commit 1ffa59a85cb simply solved the problem
that existed with foreign keys, without "joining the dots". It reused
the rowtypes.c "operator *= equality" stuff to fix the problem, but
only in an ad-hoc and undoumented way. Let's not do that again now.

Note: In theory this definition is stricter than truly necessary to
make deduplication safe, because we can imagine a contrived case in
which an operator class exists where datum_image_eq() does not always
agree with the equality operator, even though the equality operator
will reliably consider two datums to be equal only when they have
identical outputs from the underlying type's output function. This
could happen when an operator class author wasn't very careful about
zeroing padding -- this may not have mattered to the opclass author
because nobody relied on that padding anyway. I think that stuff like
this is not worth worrying about -- it can only happen because the
datatype/operator class author was very sloppy.

Note also: We seem to make this assumption already. Maybe this
uninitialized bytes side issue doesn't even need to be pointed out or
discussed. The comment just above VALGRIND_CHECK_MEM_IS_DEFINED()
within PageAddItemExtended() seems to suggest this. The comment
specifically mentions datumIsEqual() (not datum_image_eq()), but it's
exactly the same issue.

[1]: https://www.postgresql.org/docs/devel/functions-comparisons.html#COMPOSITE-TYPE-COMPARISON
[2]: /messages/by-id/3326fc2e-bc02-d4c5-e3e5-e54da466e89a@2ndquadrant.com

--
Peter Geoghegan

#27Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#26)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Mon, Dec 30, 2019 at 6:58 PM Peter Geoghegan <pg@bowt.ie> wrote:

I propose that we adopt the following definition: For an operator
class to be safe, its equality operator has to always agree with
datum_image_eq() (i.e. two datums must be bitwise equal after
detoasting).

I suggested using datumIsEqual() as the canonical definition. (I
wonder why datum_image_eq() does not reuse that function?)

Note: In theory this definition is stricter than truly necessary to
make deduplication safe, because we can imagine a contrived case in
which an operator class exists where datum_image_eq() does not always
agree with the equality operator, even though the equality operator
will reliably consider two datums to be equal only when they have
identical outputs from the underlying type's output function. This
could happen when an operator class author wasn't very careful about
zeroing padding -- this may not have mattered to the opclass author
because nobody relied on that padding anyway. I think that stuff like
this is not worth worrying about -- it can only happen because the
datatype/operator class author was very sloppy.

+1.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

In reply to: Robert Haas (#27)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Thu, Jan 2, 2020 at 6:42 AM Robert Haas <robertmhaas@gmail.com> wrote:

On Mon, Dec 30, 2019 at 6:58 PM Peter Geoghegan <pg@bowt.ie> wrote:

I propose that we adopt the following definition: For an operator
class to be safe, its equality operator has to always agree with
datum_image_eq() (i.e. two datums must be bitwise equal after
detoasting).

I suggested using datumIsEqual() as the canonical definition. (I
wonder why datum_image_eq() does not reuse that function?)

The difference between datum_image_eq() and datumIsEqual() is that
only the former will consider two datums equal when they happen to
have different TOAST input states -- we need that here. datumIsEqual()
avoids doing this because sometimes it needs to work for callers
operating within an aborted transaction. datum_image_eq() was
originally used for the "*=, *<>, *<, *<=, *>, and *>=" rowtype B-Tree
operator class needed by REFRESH MATERIALIZED VIEW CONCURRENTLY.
(Actually, that's not quite true, since datum_image_eq() is a spin-off
of the rowtype code that was added much more recently to fix a bug in
foreign keys.)

The B-Tree code and amcheck need to be tolerant of inconsistent TOAST
input states. This isn't particularly likely to happen, but it would
be hard to revoke the general assumption that that's okay now. Also,
it's not that hard to deal with it directly. For example, we're not
reliant on equal index tuples all being the same size in the
deduplication patch.

--
Peter Geoghegan

#29Robert Haas
robertmhaas@gmail.com
In reply to: Peter Geoghegan (#28)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Thu, Jan 2, 2020 at 12:11 PM Peter Geoghegan <pg@bowt.ie> wrote:

The difference between datum_image_eq() and datumIsEqual() is that
only the former will consider two datums equal when they happen to
have different TOAST input states -- we need that here.

Ah, OK. Sorry for the noise.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#30Anastasia Lubennikova
a.lubennikova@postgrespro.ru
In reply to: Peter Geoghegan (#25)
1 attachment(s)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On 31.12.2019 01:40, Peter Geoghegan wrote:

On Mon, Dec 30, 2019 at 9:45 AM Robert Haas <robertmhaas@gmail.com> wrote:

For example, float and numeric types are "never bitwise equal", while array,
text, and other container types are "maybe bitwise equal". An array of
integers
or text with C collation can be treated as bitwise equal attributes, and it
would be too harsh to restrict them from deduplication.

We might as well support container types (like array) in the first
Postgres version that has nbtree deduplication, I suppose. Even still,
I don't think that it actually matters much to users. B-Tree indexes
on arrays are probably very rare. Note that I don't consider text to
be a container type here -- obviously btree/text_ops is a very
important opclass for the deduplication feature. It may be the most
important opclass overall.

Recursively invoking a support function for the "contained" data type
in the btree/array_ops support function seems like it might be messy.
Not sure about that, though.

What bothers me is that this option will unlikely be helpful on its own
and we
should also provide some kind of recheck function along with opclass, which
complicates this idea even further and doesn't seem very clear.

It seems like the simplest thing might be to forget about the 'char'
column and just have a support function which can be used to assess
whether a given opclass's notion of equality is bitwise.

I like the idea of relying only on a support function.

In attachment you can find the WIP patch that adds support function for
btree opclasses.
Before continuing, I want to ensure that I understood the discussion
above correctly.

Current version of the patch adds:

1) new syntax, which allow to provide support function:

CREATE OPERATOR CLASS int4_ops_test
FOR TYPE int4 USING btree AS
        OPERATOR 1 =(int4, int4),
        FUNCTION 1 btint4cmp(int4, int4),
        SUPPORT datum_image_eqisbitwise;

We probably can add more words to specify the purpose of the support
function.
Do you have any other objections about the place of this new element in
CreateOplcass syntax structure?

2) trivial support function that always returns true
'datum_image_eqisbitwise'.
It is named after 'datum_image_eq', because we define this support
function via its behavior.

If this prototype is fine, I will continue this work and add support
functions for other opclasses, update pg_dump and documentation.

Thoughts?

Attachments:

v5-WIP-Opclass-bitwise-equality-0001.patchtext/x-patch; charset=UTF-8; name=v5-WIP-Opclass-bitwise-equality-0001.patchDownload
commit 9eb37de922c699208e52349494653241ddeb0116
Author: anastasia <a.lubennikova@postgrespro.ru>
Date:   Mon Jan 13 23:28:20 2020 +0300

    v5-WIP-Opclass-bitwise-equality-0001.patch

diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index e2c6de457c..143ecd1e13 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -335,7 +335,8 @@ DefineOpClass(CreateOpClassStmt *stmt)
 				storageoid,		/* storage datatype oid, if any */
 				namespaceoid,	/* namespace to create opclass in */
 				opfamilyoid,	/* oid of containing opfamily */
-				opclassoid;		/* oid of opclass we create */
+				opclassoid,		/* oid of opclass we create */
+				eqisbitwise_procoid; /* oid of opclass support function */
 	int			maxOpNumber,	/* amstrategies value */
 				maxProcNumber;	/* amsupport value */
 	bool		amstorage;		/* amstorage flag */
@@ -564,6 +565,9 @@ DefineOpClass(CreateOpClassStmt *stmt)
 					aclcheck_error_type(ACLCHECK_NOT_OWNER, storageoid);
 #endif
 				break;
+			case OPCLASS_ITEM_SUPPORT_FUNCTION:
+				eqisbitwise_procoid = LookupFuncName(item->name->objname, 0, false, false);
+				break;
 			default:
 				elog(ERROR, "unrecognized item type: %d", item->itemtype);
 				break;
@@ -653,6 +657,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	values[Anum_pg_opclass_opcintype - 1] = ObjectIdGetDatum(typeoid);
 	values[Anum_pg_opclass_opcdefault - 1] = BoolGetDatum(stmt->isDefault);
 	values[Anum_pg_opclass_opckeytype - 1] = ObjectIdGetDatum(storageoid);
+	values[Anum_pg_opclass_opceqisbitwise - 1] = ObjectIdGetDatum(eqisbitwise_procoid);
 
 	tup = heap_form_tuple(rel->rd_att, values, nulls);
 
@@ -707,6 +712,15 @@ DefineOpClass(CreateOpClassStmt *stmt)
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
+	/* dependency on support function */
+	if (OidIsValid(eqisbitwise_procoid))
+	{
+		referenced.classId = ProcedureRelationId;
+		referenced.objectId = eqisbitwise_procoid;
+		referenced.objectSubId = 0;
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
 	/* dependency on owner */
 	recordDependencyOnOwner(OperatorClassRelationId, opclassoid, GetUserId());
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 54ad62bb7f..b0cae4e0aa 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3793,6 +3793,7 @@ _copyCreateOpClassStmt(const CreateOpClassStmt *from)
 	COPY_NODE_FIELD(datatype);
 	COPY_NODE_FIELD(items);
 	COPY_SCALAR_FIELD(isDefault);
+	COPY_STRING_FIELD(eqisbitwise_fn);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5b1ba143b1..eac66c6b84 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1613,6 +1613,7 @@ _equalCreateOpClassStmt(const CreateOpClassStmt *a, const CreateOpClassStmt *b)
 	COMPARE_NODE_FIELD(datatype);
 	COMPARE_NODE_FIELD(items);
 	COMPARE_SCALAR_FIELD(isDefault);
+	COMPARE_STRING_FIELD(eqisbitwise_fn);
 
 	return true;
 }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ad5be902b0..7fa9c0c14b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6019,6 +6019,13 @@ opclass_item:
 					n->storedtype = $2;
 					$$ = (Node *) n;
 				}
+			| SUPPORT function_with_argtypes
+				{
+					CreateOpClassItem *n = makeNode(CreateOpClassItem);
+					n->itemtype = OPCLASS_ITEM_SUPPORT_FUNCTION;
+					n->name = $2;
+					$$ = (Node *) n;
+				}
 		;
 
 opt_default:	DEFAULT						{ $$ = true; }
diff --git a/src/backend/utils/adt/datum.c b/src/backend/utils/adt/datum.c
index 4e81947352..f81037518f 100644
--- a/src/backend/utils/adt/datum.c
+++ b/src/backend/utils/adt/datum.c
@@ -323,6 +323,20 @@ datum_image_eq(Datum value1, Datum value2, bool typByVal, int typLen)
 	return result;
 }
 
+/*-------------------------------------------------------------------------
+ * datum_image_eqisbitwise
+ *
+ * We define opclass safety for datum image comparison via the fact that
+ * opclass equality operator is always agree with datum_image_eq.
+ * So that is the trivial case.
+ *-------------------------------------------------------------------------
+ */
+Datum
+datum_image_eqisbitwise(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(true);
+}
+
 /*-------------------------------------------------------------------------
  * datumEstimateSpace
  *
diff --git a/src/include/catalog/pg_opclass.dat b/src/include/catalog/pg_opclass.dat
index ab2f50c9eb..47d9f9da8c 100644
--- a/src/include/catalog/pg_opclass.dat
+++ b/src/include/catalog/pg_opclass.dat
@@ -21,11 +21,11 @@
 { opcmethod => 'hash', opcname => 'array_ops', opcfamily => 'hash/array_ops',
   opcintype => 'anyarray' },
 { opcmethod => 'btree', opcname => 'bit_ops', opcfamily => 'btree/bit_ops',
-  opcintype => 'bit' },
+  opcintype => 'bit' , opceqisbitwise => 'datum_image_eqisbitwise' },
 { opcmethod => 'btree', opcname => 'bool_ops', opcfamily => 'btree/bool_ops',
-  opcintype => 'bool' },
+  opcintype => 'bool' , opceqisbitwise => 'datum_image_eqisbitwise'},
 { opcmethod => 'btree', opcname => 'bpchar_ops',
-  opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar' },
+  opcfamily => 'btree/bpchar_ops', opcintype => 'bpchar'},
 { opcmethod => 'hash', opcname => 'bpchar_ops', opcfamily => 'hash/bpchar_ops',
   opcintype => 'bpchar' },
 { opcmethod => 'btree', opcname => 'bytea_ops', opcfamily => 'btree/bytea_ops',
@@ -62,17 +62,17 @@
   opcfamily => 'spgist/network_ops', opcintype => 'inet' },
 { oid => '1979', oid_symbol => 'INT2_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int2_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int2' },
+  opcintype => 'int2', opceqisbitwise => 'datum_image_eqisbitwise' },
 { opcmethod => 'hash', opcname => 'int2_ops', opcfamily => 'hash/integer_ops',
   opcintype => 'int2' },
 { oid => '1978', oid_symbol => 'INT4_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int4_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int4' },
+  opcintype => 'int4', opceqisbitwise => 'datum_image_eqisbitwise' },
 { opcmethod => 'hash', opcname => 'int4_ops', opcfamily => 'hash/integer_ops',
   opcintype => 'int4' },
 { oid => '3124', oid_symbol => 'INT8_BTREE_OPS_OID',
   opcmethod => 'btree', opcname => 'int8_ops', opcfamily => 'btree/integer_ops',
-  opcintype => 'int8' },
+  opcintype => 'int8', opceqisbitwise => 'datum_image_eqisbitwise' },
 { opcmethod => 'hash', opcname => 'int8_ops', opcfamily => 'hash/integer_ops',
   opcintype => 'int8' },
 { opcmethod => 'btree', opcname => 'interval_ops',
diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h
index 963ab3eae8..304fc59f1e 100644
--- a/src/include/catalog/pg_opclass.h
+++ b/src/include/catalog/pg_opclass.h
@@ -73,6 +73,10 @@ CATALOG(pg_opclass,2616,OperatorClassRelationId)
 
 	/* type of data in index, or InvalidOid */
 	Oid			opckeytype BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
+
+	/* support function (0 if none)*/
+	regproc		opceqisbitwise BKI_DEFAULT(-) BKI_LOOKUP(pg_proc);
+
 } FormData_pg_opclass;
 
 /* ----------------
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 427faa3c3b..4fc8aa20b5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10745,4 +10745,10 @@
   proname => 'pg_partition_root', prorettype => 'regclass',
   proargtypes => 'regclass', prosrc => 'pg_partition_root' },
 
+  # support functions for operator classes
+  # trivial cases use datum_image_eqisbitwise as source function
+{ oid => '560',
+  proname => 'datum_image_eqisbitwise', proleakproof => 't', prorettype => 'bool',
+  proargtypes => '', prosrc => 'datum_image_eqisbitwise' },
+
 ]
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index cdfa0568f7..8838250a92 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2598,11 +2598,14 @@ typedef struct CreateOpClassStmt
 	TypeName   *datatype;		/* datatype of indexed column */
 	List	   *items;			/* List of CreateOpClassItem nodes */
 	bool		isDefault;		/* Should be marked as default for type? */
+	char		*eqisbitwise_fn; /* name of support function to check if opclass
+								  * equality operator provides bitwise comparator */
 } CreateOpClassStmt;
 
 #define OPCLASS_ITEM_OPERATOR		1
 #define OPCLASS_ITEM_FUNCTION		2
 #define OPCLASS_ITEM_STORAGETYPE	3
+#define OPCLASS_ITEM_SUPPORT_FUNCTION	4
 
 typedef struct CreateOpClassItem
 {
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index c19740e5db..263cc5545b 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -468,9 +468,10 @@ SELECT p1.oid, p1.proname
 FROM pg_proc as p1 LEFT JOIN pg_description as d
      ON p1.tableoid = d.classoid and p1.oid = d.objoid and d.objsubid = 0
 WHERE d.classoid IS NULL AND p1.oid <= 9999;
- oid | proname 
------+---------
-(0 rows)
+ oid |         proname         
+-----+-------------------------
+ 560 | datum_image_eqisbitwise
+(1 row)
 
 -- List of built-in leakproof functions
 --
@@ -587,6 +588,7 @@ int84lt(bigint,integer)
 int84gt(bigint,integer)
 int84le(bigint,integer)
 int84ge(bigint,integer)
+datum_image_eqisbitwise()
 oidvectorne(oidvector,oidvector)
 namelt(name,name)
 namele(name,name)
In reply to: Anastasia Lubennikova (#30)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Mon, Jan 13, 2020 at 12:49 PM Anastasia Lubennikova
<a.lubennikova@postgrespro.ru> wrote:

In attachment you can find the WIP patch that adds support function for
btree opclasses.

Cool. Thanks!

Current version of the patch adds:

1) new syntax, which allow to provide support function:

CREATE OPERATOR CLASS int4_ops_test
FOR TYPE int4 USING btree AS
OPERATOR 1 =(int4, int4),
FUNCTION 1 btint4cmp(int4, int4),
SUPPORT datum_image_eqisbitwise;

Hmm. Do we really need these grammar changes? If so, why? I think that
you wanted to make this something that could work with any opclass of
any index access method, but I don't see that as a useful goal. (If it
was useful, it could be considered later, on a case by case basis.)

I imagined that this infrastructure would consist of inventing a new
variety of B-Tree opclass support function -- something like
sortsupport. You could generalize from the example of commit
c6e3ac11b60, which added SortSupport functions (also known as "B-Tree
support function 2" functions). You might also take a look at the much
more recent commit 0a459cec96d, which added in_range functions (also
known as "B-Tree support function 3"). Note that neither of those two
commits had grammar changes for CREATE OPERATOR CLASS, or anything
like that. What I have in mind is a "B-Tree support function 4",
obviously.

You should probably add a C function that's similar to
PrepareSortSupportFromIndexRel()/FinishSortSupportFunction() that will
be called from the B-Tree code. This will give a simple yes/no answer
to the question: "Is it safe to apply deduplication to this Relation"?
This C function will know to return false for an opclass that doesn't
have any support function 4 set for any single attribute. It can
provide a general overview of what we're telling the caller about the
opclass here, etc. Another patch could add a similar function that
works with a plain operator, a bit like
PrepareSortSupportFromOrderingOp() -- but that isn't necessary now.

I suppose that this approach requires something a bit like a struct
SortSupportData, with filled-out collation information, etc. The
nbtree code expects a simple yes/no answer based on all columns in the
index, so it will be necessary to serialize that information to send
it across the SQL function interface -- the pg_proc support function
will have one argument of type "internal". And, I suppose that you'll
also need some basic btvalidate() validation code.

We probably can add more words to specify the purpose of the support
function.

Right -- some documentation is needed in btree.sgml, alongside the
existing stuff for support functions 1, 2, and 3.

2) trivial support function that always returns true
'datum_image_eqisbitwise'.
It is named after 'datum_image_eq', because we define this support
function via its behavior.

I like the idea of a generic, trivial SQL-callable function that all
simple scalar types can use -- one that just returns true. Maybe we
should call this general class of function an "image_equal" function,
and refer to "image equality" in the btree.sgml docs. I don't think
that using the term "bitwise" is helpful, since it sounds very precise
but is actually slightly inaccurate (since TOASTable datums can be
"image equal", but not bitwise equal according to datumIsEqual()).

How does everyone feel about "image equality" as a name? As I said
before, it seems like a good idea to tie this new infrastructure to
existing infrastructure used for things like REFRESH MATERIALIZED VIEW
CONCURRENTLY.

If this prototype is fine, I will continue this work and add support
functions for other opclasses, update pg_dump and documentation.

If this work is structured as a new support function, then it isn't
really a user-visible feature -- you won't need pg_dump support, psql
support, etc. Most of the documentation will be for operator class
authors rather than regular users. We can document the specific
opclasses that have support for deduplication later, if at all.

I think it would be fine if the deduplication docs (not the docs for
this infrastructure) just pointed out specific cases that we *cannot*
support -- there are not many exceptions (numeric, text with a
nondeterministic collation, a few others like that).

--
Peter Geoghegan

In reply to: Tom Lane (#2)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Sun, Aug 25, 2019 at 1:56 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:

I agree that teaching opclasses to say whether this is okay is a
reasonable approach.

I've begun working on this, with help from Anastasia.

My working assumption is that I only need to care about
opclass-declared input data types (pg_opclass.opcintype), plus the
corresponding collations -- the former can be used to lookup an
appropriate pg_amproc entry (i.e. B-Tree support function 4), while
the latter are passed to the support function to get an answer about
whether or not it's okay to use deduplication. This approach seems to
be good enough as far as the deduplication project's needs are
concerned. However, I think that I probably need to take a broader
view of the problem than that. Any guidance would be much appreciated.

Consumers of this new infrastructure probably won't be limited to the
deduplication feature;

Indeed, we run up against this sort of thing all the time in, eg, planner
optimizations. I think some sort of "equality is precise" indicator
would be really useful for a lot of things.

Suppose I wanted to add support for deduplication of a B-Tree index on
an array of integers. This probably wouldn't be very compelling, but
just suppose. It's not clear how this could work within the confines
of the type and operator class systems.

I can hardly determine that it's safe or unsafe to do so at CREATE
INDEX time, since the opclass-declared input data type is always the
pg_type.oid corresponding to 'anyarray' -- I am forced to make a
generic assumption that deduplication is not safe. I must make this
conservative assumption since, in general, the indexed column could
turn out to be an array of numeric datums -- a "transitively unsafe"
anyarray (numeric's display scale issue could leak into anyarray). I'm
not actually worried about any practical downside that this may create
for users of the B-Tree deduplication feature; a B-Tree index on an
array *is* a pretty niche thing. Does seem like I should make sure
that I get this right, though.

Code like the 'anyarray' B-Tree support function 1 (i.e.
btarraycmp()/array_cmp()) doesn't hint at a solution -- it merely does
a lookup of the underlying type's comparator using the typcache. That
depends on having actual anyarray datums to do something with, which
isn't something that this new infrastructure can rely on in any way.

I suppose that the only thing that would work here would be to somehow
look through the pg_attribute entry for the index column, which will
have the details required to distinguish between (say) an array of
integers (which is safe, I think) from an array of numerics (which is
unsafe). From there, the information about the element type could
(say) be passed to the anyarray default opclass' support function 4,
which could do its own internal lookup. That seems like it might be a
solution in search of a problem, though.

BTW, I currently forbid cross-type support function 4 entries for an
opclass, on the grounds that that isn't sensible for deduplication. Do
you think that that restriction is appropriate in general, given the
likelihood that this support function will be used in several other
areas?

Thanks
--
Peter Geoghegan

In reply to: Peter Geoghegan (#32)
Re: Building infrastructure for B-Tree deduplication that recognizes when opclass equality is also equivalence

On Sat, Feb 8, 2020 at 6:50 PM Peter Geoghegan <pg@bowt.ie> wrote:

My working assumption is that I only need to care about
opclass-declared input data types (pg_opclass.opcintype), plus the
corresponding collations -- the former can be used to lookup an
appropriate pg_amproc entry (i.e. B-Tree support function 4), while
the latter are passed to the support function to get an answer about
whether or not it's okay to use deduplication. This approach seems to
be good enough as far as the deduplication project's needs are
concerned. However, I think that I probably need to take a broader
view of the problem than that. Any guidance would be much appreciated.

v33 of the deduplication patch series was just posted. It included
this infrastructure in a separate patch, which isn't that big on its
own. See:

/messages/by-id/CAH2-WzmQGYDDoAETGhpGtJQRv_uFHMjvQZ6JdLV-sxGoCgLBNg@mail.gmail.com

Expert review of the opclass infrastructure still seems like a good
idea. I'm sure that it does everything that the deduplication feature
will ever need, but I'm a little concerned about painting myself into
a corner as far as other things that use the API are concerned. In
particular, I hope that I haven't failed to anticipate a requirement
that the planner has for the new API.

Thanks
--
Peter Geoghegan