Re: No = operator for opfamily 426 - Mailing list pgsql-bugs

From Tom Lane
Subject Re: No = operator for opfamily 426
Date
Msg-id 27666.1574206271@sss.pgh.pa.us
Whole thread Raw
In response to Re: No = operator for opfamily 426  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-bugs
I wrote:
> Manuel Rigger <rigger.manuel@gmail.com> writes:
>> Consider the following statements:

>> CREATE TABLE t0(c0 TEXT);
>> CREATE INDEX i0 ON t0(c0 bpchar_ops);
>> SELECT * FROM t0 WHERE t0.c0 LIKE ''; -- ERROR:  no = operator for opfamily 426

> Really what the error is showing is that like_support.c is being too
> aggressive by assuming that it'll necessarily find a matching opfamily
> member.  It should probably just silently fail if it can't construct
> the opclause it wants.

I pushed a stopgap fix that just does that, but I think really what we
ought to do about this is decouple like_support.c as far as possible
from the index opclass.  The notion that we choose the target operators
based on the opclass is really backwards now that I think about it.
The operators that represent the potentially indexscannable conditions
are determined by the LIKE/regex operator, and what we should do is
just ask whether the opclass can support them.

The "pattern" opclasses put a crimp in this position, but those can
now be seen to be legacy things not a model that future code is likely
to follow.  So I present the attached proposed patch that does things
this way.  The only short-term advantage is that it can handle applying
an exact-match LIKE to a hash opclass:

regression=# create table t (f1 text);
CREATE TABLE
regression=# create index on t using hash(f1);
CREATE INDEX
regression=# explain select * from t where f1 like 'foo';
                              QUERY PLAN
-----------------------------------------------------------------------
 Bitmap Heap Scan on t  (cost=4.05..14.20 rows=7 width=32)
   Filter: (f1 ~~ 'foo'::text)
   ->  Bitmap Index Scan on t_f1_idx  (cost=0.00..4.05 rows=7 width=0)
         Index Cond: (f1 = 'foo'::text)
(4 rows)

which isn't much of a gain, admittedly.  But now this code won't need
revision when we start to think about new index AMs with new opclasses
that happen to implement btree-ish semantics.

            regards, tom lane

diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c
index 77cc378..e785435 100644
--- a/src/backend/utils/adt/like_support.c
+++ b/src/backend/utils/adt/like_support.c
@@ -39,6 +39,7 @@
 #include "access/htup_details.h"
 #include "access/stratnum.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_operator.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_type.h"
@@ -240,7 +241,10 @@ match_pattern_prefix(Node *leftop,
     Pattern_Prefix_Status pstatus;
     Oid            ldatatype;
     Oid            rdatatype;
-    Oid            oproid;
+    Oid            eqopr;
+    Oid            ltopr;
+    Oid            geopr;
+    bool        collation_aware;
     Expr       *expr;
     FmgrInfo    ltproc;
     Const       *greaterstr;
@@ -284,62 +288,89 @@ match_pattern_prefix(Node *leftop,
         return NIL;

     /*
-     * Must also check that index's opfamily supports the operators we will
-     * want to apply.  (A hash index, for example, will not support ">=".)
-     * Currently, only btree and spgist support the operators we need.
-     *
-     * Note: actually, in the Pattern_Prefix_Exact case, we only need "=" so a
-     * hash index would work.  Currently it doesn't seem worth checking for
-     * that, however.
-     *
-     * We insist on the opfamily being one of the specific ones we expect,
-     * else we'd do the wrong thing if someone were to make a reverse-sort
-     * opfamily with the same operators.
-     *
-     * The non-pattern opclasses will not sort the way we need in most non-C
-     * locales.  We can use such an index anyway for an exact match (simple
-     * equality), but not for prefix-match cases.  Note that here we are
-     * looking at the index's collation, not the expression's collation --
-     * this test is *not* dependent on the LIKE/regex operator's collation.
-     *
-     * While we're at it, identify the type the comparison constant(s) should
-     * have, based on the opfamily.
+     * Identify the operators we want to use, based on the type of the
+     * left-hand argument.  Usually these are just the type's regular
+     * comparison operators, but if we are considering one of the semi-legacy
+     * "pattern" opclasses, use the "pattern" operators instead.  Those are
+     * not collation-sensitive but always use C collation, as we want.  The
+     * selected operators also determine the needed type of the prefix
+     * constant.
      */
-    switch (opfamily)
+    ldatatype = exprType(leftop);
+    switch (ldatatype)
     {
-        case TEXT_BTREE_FAM_OID:
-            if (!(pstatus == Pattern_Prefix_Exact ||
-                  lc_collate_is_c(indexcollation)))
-                return NIL;
+        case TEXTOID:
+            if (opfamily == TEXT_PATTERN_BTREE_FAM_OID ||
+                opfamily == TEXT_SPGIST_FAM_OID)
+            {
+                eqopr = TextEqualOperator;
+                ltopr = TextPatternLessOperator;
+                geopr = TextPatternGreaterEqualOperator;
+                collation_aware = false;
+            }
+            else
+            {
+                eqopr = TextEqualOperator;
+                ltopr = TextLessOperator;
+                geopr = TextGreaterEqualOperator;
+                collation_aware = true;
+            }
             rdatatype = TEXTOID;
             break;
+        case NAMEOID:

-        case TEXT_PATTERN_BTREE_FAM_OID:
-        case TEXT_SPGIST_FAM_OID:
+            /*
+             * Note that here, we need the RHS type to be text, so that the
+             * comparison value isn't improperly truncated to NAMEDATALEN.
+             */
+            eqopr = NameEqualTextOperator;
+            ltopr = NameLessTextOperator;
+            geopr = NameGreaterEqualTextOperator;
+            collation_aware = true;
             rdatatype = TEXTOID;
             break;
-
-        case BPCHAR_BTREE_FAM_OID:
-            if (!(pstatus == Pattern_Prefix_Exact ||
-                  lc_collate_is_c(indexcollation)))
-                return NIL;
-            rdatatype = BPCHAROID;
-            break;
-
-        case BPCHAR_PATTERN_BTREE_FAM_OID:
+        case BPCHAROID:
+            if (opfamily == BPCHAR_PATTERN_BTREE_FAM_OID)
+            {
+                eqopr = BpcharEqualOperator;
+                ltopr = BpcharPatternLessOperator;
+                geopr = BpcharPatternGreaterEqualOperator;
+                collation_aware = false;
+            }
+            else
+            {
+                eqopr = BpcharEqualOperator;
+                ltopr = BpcharLessOperator;
+                geopr = BpcharGreaterEqualOperator;
+                collation_aware = true;
+            }
             rdatatype = BPCHAROID;
             break;
-
-        case BYTEA_BTREE_FAM_OID:
+        case BYTEAOID:
+            eqopr = ByteaEqualOperator;
+            ltopr = ByteaLessOperator;
+            geopr = ByteaGreaterEqualOperator;
+            collation_aware = false;
             rdatatype = BYTEAOID;
             break;
-
         default:
+            /* Can't get here unless we're attached to the wrong operator */
             return NIL;
     }

-    /* OK, prepare to create the indexqual(s) */
-    ldatatype = exprType(leftop);
+    /*
+     * If necessary, verify that the index's collation behavior is compatible.
+     * For an exact-match case, we don't have to be picky.  Otherwise, insist
+     * that the index collation be "C".  Note that here we are looking at the
+     * index's collation, not the expression's collation -- this test is *not*
+     * dependent on the LIKE/regex operator's collation.
+     */
+    if (collation_aware)
+    {
+        if (!(pstatus == Pattern_Prefix_Exact ||
+              lc_collate_is_c(indexcollation)))
+            return NIL;
+    }

     /*
      * If necessary, coerce the prefix constant to the right type.  The given
@@ -358,16 +389,17 @@ match_pattern_prefix(Node *leftop,
     /*
      * If we found an exact-match pattern, generate an "=" indexqual.
      *
-     * (Despite the checks above, we might fail to find a suitable operator in
-     * some cases with binary-compatible opclasses.  Just punt if so.)
+     * Here and below, check to see whether the desired operator is actually
+     * supported by the index opclass, and fail quietly if not.  This allows
+     * us to not be concerned with specific opclasses (except for the legacy
+     * "pattern" cases); any index that correctly implements the operators
+     * will work.
      */
     if (pstatus == Pattern_Prefix_Exact)
     {
-        oproid = get_opfamily_member(opfamily, ldatatype, rdatatype,
-                                     BTEqualStrategyNumber);
-        if (oproid == InvalidOid)
+        if (!op_in_opfamily(eqopr, opfamily))
             return NIL;
-        expr = make_opclause(oproid, BOOLOID, false,
+        expr = make_opclause(eqopr, BOOLOID, false,
                              (Expr *) leftop, (Expr *) prefix,
                              InvalidOid, indexcollation);
         result = list_make1(expr);
@@ -379,11 +411,9 @@ match_pattern_prefix(Node *leftop,
      *
      * We can always say "x >= prefix".
      */
-    oproid = get_opfamily_member(opfamily, ldatatype, rdatatype,
-                                 BTGreaterEqualStrategyNumber);
-    if (oproid == InvalidOid)
+    if (!op_in_opfamily(geopr, opfamily))
         return NIL;
-    expr = make_opclause(oproid, BOOLOID, false,
+    expr = make_opclause(geopr, BOOLOID, false,
                          (Expr *) leftop, (Expr *) prefix,
                          InvalidOid, indexcollation);
     result = list_make1(expr);
@@ -396,15 +426,13 @@ match_pattern_prefix(Node *leftop,
      * using a C-locale index collation.
      *-------
      */
-    oproid = get_opfamily_member(opfamily, ldatatype, rdatatype,
-                                 BTLessStrategyNumber);
-    if (oproid == InvalidOid)
+    if (!op_in_opfamily(ltopr, opfamily))
         return result;
-    fmgr_info(get_opcode(oproid), <proc);
+    fmgr_info(get_opcode(ltopr), <proc);
     greaterstr = make_greater_string(prefix, <proc, indexcollation);
     if (greaterstr)
     {
-        expr = make_opclause(oproid, BOOLOID, false,
+        expr = make_opclause(ltopr, BOOLOID, false,
                              (Expr *) leftop, (Expr *) greaterstr,
                              InvalidOid, indexcollation);
         result = lappend(result, expr);
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index fa7dc96..ba9c09d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -107,12 +107,12 @@
   oprcode => 'starts_with', oprrest => 'prefixsel',
   oprjoin => 'prefixjoinsel' },

-{ oid => '254', descr => 'equal',
+{ oid => '254', oid_symbol => 'NameEqualTextOperator',  descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'name',
   oprright => 'text', oprresult => 'bool', oprcom => '=(text,name)',
   oprnegate => '<>(name,text)', oprcode => 'nameeqtext', oprrest => 'eqsel',
   oprjoin => 'eqjoinsel' },
-{ oid => '255', descr => 'less than',
+{ oid => '255', oid_symbol => 'NameLessTextOperator', descr => 'less than',
   oprname => '<', oprleft => 'name', oprright => 'text', oprresult => 'bool',
   oprcom => '>(text,name)', oprnegate => '>=(name,text)',
   oprcode => 'namelttext', oprrest => 'scalarltsel',
@@ -122,7 +122,7 @@
   oprcom => '>=(text,name)', oprnegate => '>(name,text)',
   oprcode => 'nameletext', oprrest => 'scalarlesel',
   oprjoin => 'scalarlejoinsel' },
-{ oid => '257', descr => 'greater than or equal',
+{ oid => '257', oid_symbol => 'NameGreaterEqualTextOperator', descr => 'greater than or equal',
   oprname => '>=', oprleft => 'name', oprright => 'text', oprresult => 'bool',
   oprcom => '<=(text,name)', oprnegate => '<(name,text)',
   oprcode => 'namegetext', oprrest => 'scalargesel',
@@ -789,7 +789,7 @@
   oprname => '>=', oprleft => 'name', oprright => 'name', oprresult => 'bool',
   oprcom => '<=(name,name)', oprnegate => '<(name,name)', oprcode => 'namege',
   oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' },
-{ oid => '664', descr => 'less than',
+{ oid => '664', oid_symbol => 'TextLessOperator', descr => 'less than',
   oprname => '<', oprleft => 'text', oprright => 'text', oprresult => 'bool',
   oprcom => '>(text,text)', oprnegate => '>=(text,text)', oprcode => 'text_lt',
   oprrest => 'scalarltsel', oprjoin => 'scalarltjoinsel' },
@@ -801,7 +801,7 @@
   oprname => '>', oprleft => 'text', oprright => 'text', oprresult => 'bool',
   oprcom => '<(text,text)', oprnegate => '<=(text,text)', oprcode => 'text_gt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
-{ oid => '667', descr => 'greater than or equal',
+{ oid => '667', oid_symbol => 'TextGreaterEqualOperator', descr => 'greater than or equal',
   oprname => '>=', oprleft => 'text', oprright => 'text', oprresult => 'bool',
   oprcom => '<=(text,text)', oprnegate => '<(text,text)', oprcode => 'text_ge',
   oprrest => 'scalargesel', oprjoin => 'scalargejoinsel' },
@@ -1160,7 +1160,7 @@
   oprname => '@@', oprkind => 'l', oprleft => '0', oprright => 'polygon',
   oprresult => 'point', oprcode => 'poly_center' },

-{ oid => '1054', descr => 'equal',
+{ oid => '1054', oid_symbol => 'BpcharEqualOperator', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'bpchar',
   oprright => 'bpchar', oprresult => 'bool', oprcom => '=(bpchar,bpchar)',
   oprnegate => '<>(bpchar,bpchar)', oprcode => 'bpchareq', oprrest => 'eqsel',
@@ -1180,7 +1180,7 @@
   oprresult => 'bool', oprcom => '<>(bpchar,bpchar)',
   oprnegate => '=(bpchar,bpchar)', oprcode => 'bpcharne', oprrest => 'neqsel',
   oprjoin => 'neqjoinsel' },
-{ oid => '1058', descr => 'less than',
+{ oid => '1058', oid_symbol => 'BpcharLessOperator', descr => 'less than',
   oprname => '<', oprleft => 'bpchar', oprright => 'bpchar',
   oprresult => 'bool', oprcom => '>(bpchar,bpchar)',
   oprnegate => '>=(bpchar,bpchar)', oprcode => 'bpcharlt',
@@ -1195,7 +1195,7 @@
   oprresult => 'bool', oprcom => '<(bpchar,bpchar)',
   oprnegate => '<=(bpchar,bpchar)', oprcode => 'bpchargt',
   oprrest => 'scalargtsel', oprjoin => 'scalargtjoinsel' },
-{ oid => '1061', descr => 'greater than or equal',
+{ oid => '1061', oid_symbol => 'BpcharGreaterEqualOperator', descr => 'greater than or equal',
   oprname => '>=', oprleft => 'bpchar', oprright => 'bpchar',
   oprresult => 'bool', oprcom => '<=(bpchar,bpchar)',
   oprnegate => '<(bpchar,bpchar)', oprcode => 'bpcharge',
@@ -2330,7 +2330,7 @@
   oprresult => 'numeric', oprcode => 'numeric_uplus' },

 # bytea operators
-{ oid => '1955', descr => 'equal',
+{ oid => '1955', oid_symbol => 'ByteaEqualOperator', descr => 'equal',
   oprname => '=', oprcanmerge => 't', oprcanhash => 't', oprleft => 'bytea',
   oprright => 'bytea', oprresult => 'bool', oprcom => '=(bytea,bytea)',
   oprnegate => '<>(bytea,bytea)', oprcode => 'byteaeq', oprrest => 'eqsel',
@@ -2339,7 +2339,7 @@
   oprname => '<>', oprleft => 'bytea', oprright => 'bytea', oprresult => 'bool',
   oprcom => '<>(bytea,bytea)', oprnegate => '=(bytea,bytea)',
   oprcode => 'byteane', oprrest => 'neqsel', oprjoin => 'neqjoinsel' },
-{ oid => '1957', descr => 'less than',
+{ oid => '1957', oid_symbol => 'ByteaLessOperator', descr => 'less than',
   oprname => '<', oprleft => 'bytea', oprright => 'bytea', oprresult => 'bool',
   oprcom => '>(bytea,bytea)', oprnegate => '>=(bytea,bytea)',
   oprcode => 'bytealt', oprrest => 'scalarltsel',
@@ -2354,7 +2354,7 @@
   oprcom => '<(bytea,bytea)', oprnegate => '<=(bytea,bytea)',
   oprcode => 'byteagt', oprrest => 'scalargtsel',
   oprjoin => 'scalargtjoinsel' },
-{ oid => '1960', descr => 'greater than or equal',
+{ oid => '1960', oid_symbol => 'ByteaGreaterEqualOperator', descr => 'greater than or equal',
   oprname => '>=', oprleft => 'bytea', oprright => 'bytea', oprresult => 'bool',
   oprcom => '<=(bytea,bytea)', oprnegate => '<(bytea,bytea)',
   oprcode => 'byteage', oprrest => 'scalargesel',
@@ -2416,7 +2416,7 @@
   oprresult => 'timestamp', oprcode => 'timestamp_mi_interval' },

 # character-by-character (not collation order) comparison operators for character types
-{ oid => '2314', descr => 'less than',
+{ oid => '2314', oid_symbol => 'TextPatternLessOperator', descr => 'less than',
   oprname => '~<~', oprleft => 'text', oprright => 'text', oprresult => 'bool',
   oprcom => '~>~(text,text)', oprnegate => '~>=~(text,text)',
   oprcode => 'text_pattern_lt', oprrest => 'scalarltsel',
@@ -2426,7 +2426,7 @@
   oprcom => '~>=~(text,text)', oprnegate => '~>~(text,text)',
   oprcode => 'text_pattern_le', oprrest => 'scalarlesel',
   oprjoin => 'scalarlejoinsel' },
-{ oid => '2317', descr => 'greater than or equal',
+{ oid => '2317', oid_symbol => 'TextPatternGreaterEqualOperator', descr => 'greater than or equal',
   oprname => '~>=~', oprleft => 'text', oprright => 'text', oprresult => 'bool',
   oprcom => '~<=~(text,text)', oprnegate => '~<~(text,text)',
   oprcode => 'text_pattern_ge', oprrest => 'scalargesel',
@@ -2437,7 +2437,7 @@
   oprcode => 'text_pattern_gt', oprrest => 'scalargtsel',
   oprjoin => 'scalargtjoinsel' },

-{ oid => '2326', descr => 'less than',
+{ oid => '2326', oid_symbol => 'BpcharPatternLessOperator', descr => 'less than',
   oprname => '~<~', oprleft => 'bpchar', oprright => 'bpchar',
   oprresult => 'bool', oprcom => '~>~(bpchar,bpchar)',
   oprnegate => '~>=~(bpchar,bpchar)', oprcode => 'bpchar_pattern_lt',
@@ -2447,7 +2447,7 @@
   oprresult => 'bool', oprcom => '~>=~(bpchar,bpchar)',
   oprnegate => '~>~(bpchar,bpchar)', oprcode => 'bpchar_pattern_le',
   oprrest => 'scalarlesel', oprjoin => 'scalarlejoinsel' },
-{ oid => '2329', descr => 'greater than or equal',
+{ oid => '2329', oid_symbol => 'BpcharPatternGreaterEqualOperator', descr => 'greater than or equal',
   oprname => '~>=~', oprleft => 'bpchar', oprright => 'bpchar',
   oprresult => 'bool', oprcom => '~<=~(bpchar,bpchar)',
   oprnegate => '~<~(bpchar,bpchar)', oprcode => 'bpchar_pattern_ge',

pgsql-bugs by date:

Previous
From: Andres Freund
Date:
Subject: Re: BUG #16125: Crash of PostgreSQL's wal sender during logicalreplication
Next
From: Thomas Munro
Date:
Subject: Re: BUG #16104: Invalid DSA Memory Alloc Request in Parallel Hash