Hi,

while working on the new BRIN opclasses in [1], I stumbled on something
I think is actually a bug in how CREATE OPERATOR CLASS handles the
storage type. If you look at built-in opclasses, there's a bunch of
cases where (opcintype == opckeytype), for example the BRIN opclasses
for inet data type:

test=# select oid, opcname, opcfamily, opcintype, opckeytype from
pg_opclass where opcintype = 869 order by oid;

  oid  |        opcname        | opcfamily | opcintype | opckeytype
-------+-----------------------+-----------+-----------+------------
 10105 | inet_minmax_ops       |      4075 |       869 |        869
 10106 | inet_inclusion_ops    |      4102 |       869 |        869

The fact that opckeytype is set is important, because this allows the
opclass to handle data with cidr data type - there's no opclass for
cidr, but we can do this:

create table t (a cidr);
insert into t values ('127.0.0.1');
insert into t values ('127.0.0.2');
insert into t values ('127.0.0.3');
create index on t using brin (a inet_minmax_ops);

This seems to work fine. Now, if you manually update the opckeytype to
0, you'll get this:

test=# update pg_opclass set opckeytype = 0 where oid = 10105;
UPDATE 1
test=# create index on t using brin (a inet_minmax_ops);
ERROR:  missing operator 1(650,650) in opfamily 4075


Unfortunately, it turns out it's impossible to re-create this opclass
using CREATE OPERATOR CLASS, because the opclasscmds.c does this:

    /* Just drop the spec if same as column datatype */
    if (storageoid == typeoid && false)
        storageoid = InvalidOid;

So the storageoid is reset to 0. This only works for the built-in
opclasses because those are injected directly into the catalogs.

Either the CREATE OPERATOR CLASS is not considering something before
resetting the storageoid, or maybe it should be reset for all opclasses
(including the built-in ones) and the code that's using it should
restore it in those cases (AFAICS opckeytype=0 means it's the same as
opcintkey).

Attached is a PoC patch doing the first thing - this does the trick for
me, but I'm not 100% sure it's the right fix.


[1]
https://www.postgresql.org/message-id/54b47e66-bd8a-d44a-2090-fd4b2f49abe6%40enterprisedb.com

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
>From 4ee5e2bbd0c44c82d1604836db5352f3e0e0fbf2 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas.von...@postgresql.org>
Date: Mon, 22 Mar 2021 20:36:59 +0100
Subject: [PATCH 1/5] fixup opclass storage type

---
 src/backend/commands/opclasscmds.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index fad39e2b75..78b2d69782 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -576,10 +576,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	 */
 	if (OidIsValid(storageoid))
 	{
-		/* Just drop the spec if same as column datatype */
-		if (storageoid == typeoid)
-			storageoid = InvalidOid;
-		else if (!amstorage)
+		if ((storageoid != typeoid) && (!amstorage))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 					 errmsg("storage type cannot be different from data type for access method \"%s\"",
-- 
2.30.2

Reply via email to