Add bool GiST opclass to btree_gist
authorTomas Vondra <tomas.vondra@postgresql.org>
Sat, 6 Nov 2021 16:00:43 +0000 (17:00 +0100)
committerTomas Vondra <tomas.vondra@postgresql.org>
Sat, 6 Nov 2021 16:00:43 +0000 (17:00 +0100)
Adds bool opclass to btree_gist extension, to allow creating GiST
indexes on bool columns. GiST indexes on a single bool column don't seem
particularly useful, but this allows defining exclusion constraings
involving a bool column, for example.

Author: Emre Hasegeli
Reviewed-by: Andrey Borodin
Discussion: https://postgr.es/m/CAE2gYzyDKJBZngssR84VGZEN=Ux=V9FV23QfPgo+7-yYnKKg4g@mail.gmail.com

contrib/btree_gist/Makefile
contrib/btree_gist/btree_bool.c [new file with mode: 0644]
contrib/btree_gist/btree_gist--1.6--1.7.sql [new file with mode: 0644]
contrib/btree_gist/btree_gist.control
contrib/btree_gist/btree_gist.h
contrib/btree_gist/btree_utils_num.c
contrib/btree_gist/expected/bool.out [new file with mode: 0644]
contrib/btree_gist/sql/bool.sql [new file with mode: 0644]

index e92d974a1a3bf77db4b8449e35f29f75a19ba98b..48997c75f633cc5d10695d2de0a3c2a4bbfda4af 100644 (file)
@@ -5,6 +5,7 @@ MODULE_big = btree_gist
 OBJS =  \
    $(WIN32RES) \
    btree_bit.o \
+   btree_bool.o \
    btree_bytea.o \
    btree_cash.o \
    btree_date.o \
@@ -32,12 +33,12 @@ EXTENSION = btree_gist
 DATA = btree_gist--1.0--1.1.sql \
        btree_gist--1.1--1.2.sql btree_gist--1.2.sql btree_gist--1.2--1.3.sql \
        btree_gist--1.3--1.4.sql btree_gist--1.4--1.5.sql \
-       btree_gist--1.5--1.6.sql
+       btree_gist--1.5--1.6.sql btree_gist--1.6--1.7.sql
 PGFILEDESC = "btree_gist - B-tree equivalent GiST operator classes"
 
 REGRESS = init int2 int4 int8 float4 float8 cash oid timestamp timestamptz \
         time timetz date interval macaddr macaddr8 inet cidr text varchar char \
-        bytea bit varbit numeric uuid not_equal enum
+        bytea bit varbit numeric uuid not_equal enum bool
 
 SHLIB_LINK += $(filter -lm, $(LIBS))
 
diff --git a/contrib/btree_gist/btree_bool.c b/contrib/btree_gist/btree_bool.c
new file mode 100644 (file)
index 0000000..25ce1e2
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * contrib/btree_gist/btree_bool.c
+ */
+#include "postgres.h"
+
+#include "btree_gist.h"
+#include "btree_utils_num.h"
+#include "common/int.h"
+
+typedef struct boolkey
+{
+   bool        lower;
+   bool        upper;
+} boolKEY;
+
+/*
+** bool ops
+*/
+PG_FUNCTION_INFO_V1(gbt_bool_compress);
+PG_FUNCTION_INFO_V1(gbt_bool_fetch);
+PG_FUNCTION_INFO_V1(gbt_bool_union);
+PG_FUNCTION_INFO_V1(gbt_bool_picksplit);
+PG_FUNCTION_INFO_V1(gbt_bool_consistent);
+PG_FUNCTION_INFO_V1(gbt_bool_penalty);
+PG_FUNCTION_INFO_V1(gbt_bool_same);
+
+static bool
+gbt_boolgt(const void *a, const void *b, FmgrInfo *flinfo)
+{
+   return (*((const bool *) a) > *((const bool *) b));
+}
+static bool
+gbt_boolge(const void *a, const void *b, FmgrInfo *flinfo)
+{
+   return (*((const bool *) a) >= *((const bool *) b));
+}
+static bool
+gbt_booleq(const void *a, const void *b, FmgrInfo *flinfo)
+{
+   return (*((const bool *) a) == *((const bool *) b));
+}
+static bool
+gbt_boolle(const void *a, const void *b, FmgrInfo *flinfo)
+{
+   return (*((const bool *) a) <= *((const bool *) b));
+}
+static bool
+gbt_boollt(const void *a, const void *b, FmgrInfo *flinfo)
+{
+   return (*((const bool *) a) < *((const bool *) b));
+}
+
+static int
+gbt_boolkey_cmp(const void *a, const void *b, FmgrInfo *flinfo)
+{
+   boolKEY   *ia = (boolKEY *) (((const Nsrt *) a)->t);
+   boolKEY   *ib = (boolKEY *) (((const Nsrt *) b)->t);
+
+   if (ia->lower == ib->lower)
+   {
+       if (ia->upper == ib->upper)
+           return 0;
+
+       return (ia->upper > ib->upper) ? 1 : -1;
+   }
+
+   return (ia->lower > ib->lower) ? 1 : -1;
+}
+
+
+static const gbtree_ninfo tinfo =
+{
+   gbt_t_bool,
+   sizeof(bool),
+   4,                          /* sizeof(gbtreekey4) */
+   gbt_boolgt,
+   gbt_boolge,
+   gbt_booleq,
+   gbt_boolle,
+   gbt_boollt,
+   gbt_boolkey_cmp,
+};
+
+
+/**************************************************
+ * bool ops
+ **************************************************/
+
+
+Datum
+gbt_bool_compress(PG_FUNCTION_ARGS)
+{
+   GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+
+   PG_RETURN_POINTER(gbt_num_compress(entry, &tinfo));
+}
+
+Datum
+gbt_bool_fetch(PG_FUNCTION_ARGS)
+{
+   GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+
+   PG_RETURN_POINTER(gbt_num_fetch(entry, &tinfo));
+}
+
+Datum
+gbt_bool_consistent(PG_FUNCTION_ARGS)
+{
+   GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+   bool        query = PG_GETARG_INT16(1);
+   StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+
+   /* Oid      subtype = PG_GETARG_OID(3); */
+   bool       *recheck = (bool *) PG_GETARG_POINTER(4);
+   boolKEY    *kkk = (boolKEY *) DatumGetPointer(entry->key);
+   GBT_NUMKEY_R key;
+
+   /* All cases served by this function are exact */
+   *recheck = false;
+
+   key.lower = (GBT_NUMKEY *) &kkk->lower;
+   key.upper = (GBT_NUMKEY *) &kkk->upper;
+
+   PG_RETURN_BOOL(gbt_num_consistent(&key, (void *) &query, &strategy,
+                                     GIST_LEAF(entry), &tinfo, fcinfo->flinfo));
+}
+
+
+Datum
+gbt_bool_union(PG_FUNCTION_ARGS)
+{
+   GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
+   void       *out = palloc(sizeof(boolKEY));
+
+   *(int *) PG_GETARG_POINTER(1) = sizeof(boolKEY);
+   PG_RETURN_POINTER(gbt_num_union((void *) out, entryvec, &tinfo, fcinfo->flinfo));
+}
+
+
+Datum
+gbt_bool_penalty(PG_FUNCTION_ARGS)
+{
+   boolKEY    *origentry = (boolKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(0))->key);
+   boolKEY    *newentry = (boolKEY *) DatumGetPointer(((GISTENTRY *) PG_GETARG_POINTER(1))->key);
+   float      *result = (float *) PG_GETARG_POINTER(2);
+
+   penalty_num(result, origentry->lower, origentry->upper, newentry->lower, newentry->upper);
+
+   PG_RETURN_POINTER(result);
+}
+
+Datum
+gbt_bool_picksplit(PG_FUNCTION_ARGS)
+{
+   PG_RETURN_POINTER(gbt_num_picksplit((GistEntryVector *) PG_GETARG_POINTER(0),
+                                       (GIST_SPLITVEC *) PG_GETARG_POINTER(1),
+                                       &tinfo, fcinfo->flinfo));
+}
+
+Datum
+gbt_bool_same(PG_FUNCTION_ARGS)
+{
+   boolKEY    *b1 = (boolKEY *) PG_GETARG_POINTER(0);
+   boolKEY    *b2 = (boolKEY *) PG_GETARG_POINTER(1);
+   bool       *result = (bool *) PG_GETARG_POINTER(2);
+
+   *result = gbt_num_same((void *) b1, (void *) b2, &tinfo, fcinfo->flinfo);
+   PG_RETURN_POINTER(result);
+}
diff --git a/contrib/btree_gist/btree_gist--1.6--1.7.sql b/contrib/btree_gist/btree_gist--1.6--1.7.sql
new file mode 100644 (file)
index 0000000..0e68356
--- /dev/null
@@ -0,0 +1,62 @@
+/* contrib/btree_gist/btree_gist--1.6--1.7.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "ALTER EXTENSION btree_gist UPDATE TO '1.7'" to load this file. \quit
+
+-- This upgrade scripts add support for bool.
+
+-- Define the GiST support methods
+CREATE FUNCTION gbt_bool_consistent(internal,bool,int2,oid,internal)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bool_compress(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bool_fetch(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bool_penalty(internal,internal,internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bool_picksplit(internal, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bool_union(internal, internal)
+RETURNS gbtreekey8
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+CREATE FUNCTION gbt_bool_same(gbtreekey8, gbtreekey8, internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE STRICT;
+
+-- Create the operator class
+CREATE OPERATOR CLASS gist_bool_ops
+DEFAULT FOR TYPE bool USING gist
+AS
+   OPERATOR    1   <  ,
+   OPERATOR    2   <= ,
+   OPERATOR    3   =  ,
+   OPERATOR    4   >= ,
+   OPERATOR    5   >  ,
+   OPERATOR    6   <> ,
+   FUNCTION    1   gbt_bool_consistent (internal, bool, int2, oid, internal),
+   FUNCTION    2   gbt_bool_union (internal, internal),
+   FUNCTION    3   gbt_bool_compress (internal),
+   FUNCTION    4   gbt_decompress (internal),
+   FUNCTION    5   gbt_bool_penalty (internal, internal, internal),
+   FUNCTION    6   gbt_bool_picksplit (internal, internal),
+   FUNCTION    7   gbt_bool_same (gbtreekey8, gbtreekey8, internal),
+   FUNCTION    9   gbt_bool_fetch (internal),
+   STORAGE     gbtreekey8;
index e5c41fe8f39783247d494d38318bcbb54d672b0b..fa9171a80a2e374866d2b927269c81ab9e1ca3fc 100644 (file)
@@ -1,6 +1,6 @@
 # btree_gist extension
 comment = 'support for indexing common datatypes in GiST'
-default_version = '1.6'
+default_version = '1.7'
 module_pathname = '$libdir/btree_gist'
 relocatable = true
 trusted = true
index 14c7c8ee193a427f4c6c03a754d18ff00f13a8e5..f22f14ac4ca14b4669615bacee9825294fc1c56b 100644 (file)
@@ -32,6 +32,7 @@ enum gbtree_type
    gbt_t_bpchar,
    gbt_t_bytea,
    gbt_t_bit,
+   gbt_t_bool,
    gbt_t_inet,
    gbt_t_uuid,
    gbt_t_enum
index 7564a403c7db1068470983034c186308dfe76875..5632ee0586ca8b963234dadbc74cc932e4e471f6 100644 (file)
@@ -19,6 +19,7 @@ gbt_num_compress(GISTENTRY *entry, const gbtree_ninfo *tinfo)
    {
        union
        {
+           bool        bo;
            int16       i2;
            int32       i4;
            int64       i8;
@@ -35,6 +36,10 @@ gbt_num_compress(GISTENTRY *entry, const gbtree_ninfo *tinfo)
 
        switch (tinfo->t)
        {
+           case gbt_t_bool:
+               v.bo = DatumGetBool(entry->key);
+               leaf = &v.bo;
+               break;
            case gbt_t_int2:
                v.i2 = DatumGetInt16(entry->key);
                leaf = &v.i2;
@@ -113,6 +118,9 @@ gbt_num_fetch(GISTENTRY *entry, const gbtree_ninfo *tinfo)
     */
    switch (tinfo->t)
    {
+       case gbt_t_bool:
+           datum = BoolGetDatum(*(bool *) entry->key);
+           break;
        case gbt_t_int2:
            datum = Int16GetDatum(*(int16 *) entry->key);
            break;
diff --git a/contrib/btree_gist/expected/bool.out b/contrib/btree_gist/expected/bool.out
new file mode 100644 (file)
index 0000000..c67a968
--- /dev/null
@@ -0,0 +1,96 @@
+-- bool check
+CREATE TABLE booltmp (a bool);
+INSERT INTO booltmp VALUES (false), (true);
+SET enable_seqscan=on;
+SELECT count(*) FROM booltmp WHERE a <  true;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a <= true;
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a  = true;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a >= true;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a >  true;
+ count 
+-------
+     0
+(1 row)
+
+CREATE INDEX boolidx ON booltmp USING gist ( a );
+SET enable_seqscan=off;
+SELECT count(*) FROM booltmp WHERE a <  true;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a <= true;
+ count 
+-------
+     2
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a  = true;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a >= true;
+ count 
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM booltmp WHERE a >  true;
+ count 
+-------
+     0
+(1 row)
+
+-- Test index-only scans
+SET enable_bitmapscan=off;
+EXPLAIN (COSTS OFF)
+SELECT * FROM booltmp WHERE a;
+                QUERY PLAN                
+------------------------------------------
+ Index Only Scan using boolidx on booltmp
+   Filter: a
+(2 rows)
+
+SELECT * FROM booltmp WHERE a;
+ a 
+---
+ t
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM booltmp WHERE NOT a;
+                QUERY PLAN                
+------------------------------------------
+ Index Only Scan using boolidx on booltmp
+   Filter: (NOT a)
+(2 rows)
+
+SELECT * FROM booltmp WHERE NOT a;
+ a 
+---
+ f
+(1 row)
+
diff --git a/contrib/btree_gist/sql/bool.sql b/contrib/btree_gist/sql/bool.sql
new file mode 100644 (file)
index 0000000..4c2a539
--- /dev/null
@@ -0,0 +1,42 @@
+-- bool check
+
+CREATE TABLE booltmp (a bool);
+
+INSERT INTO booltmp VALUES (false), (true);
+
+SET enable_seqscan=on;
+
+SELECT count(*) FROM booltmp WHERE a <  true;
+
+SELECT count(*) FROM booltmp WHERE a <= true;
+
+SELECT count(*) FROM booltmp WHERE a  = true;
+
+SELECT count(*) FROM booltmp WHERE a >= true;
+
+SELECT count(*) FROM booltmp WHERE a >  true;
+
+CREATE INDEX boolidx ON booltmp USING gist ( a );
+
+SET enable_seqscan=off;
+
+SELECT count(*) FROM booltmp WHERE a <  true;
+
+SELECT count(*) FROM booltmp WHERE a <= true;
+
+SELECT count(*) FROM booltmp WHERE a  = true;
+
+SELECT count(*) FROM booltmp WHERE a >= true;
+
+SELECT count(*) FROM booltmp WHERE a >  true;
+
+-- Test index-only scans
+SET enable_bitmapscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM booltmp WHERE a;
+SELECT * FROM booltmp WHERE a;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM booltmp WHERE NOT a;
+SELECT * FROM booltmp WHERE NOT a;