Add a hash function for "numeric". Mark the equality operator for
authorNeil Conway <neilc@samurai.com>
Tue, 8 May 2007 18:56:48 +0000 (18:56 +0000)
committerNeil Conway <neilc@samurai.com>
Tue, 8 May 2007 18:56:48 +0000 (18:56 +0000)
numerics as "oprcanhash", and make the corresponding system catalog
updates. As a result, hash indexes, hashed aggregation, and hash
joins can now be used with the numeric type. Bump the catversion.

The only tricky aspect to doing this is writing a correct hash
function: it's possible for two Numerics to be equal according to
their equality operator, but have different in-memory bit patterns.
To cope with this, the hash function doesn't consider the Numeric's
"scale" or "sign", and explictly skips any leading or trailing
zeros in the Numeric's digit buffer (the current implementation
should suppress any such zeros, but it seems unwise to rely upon
this). See discussion on pgsql-patches for more details.

src/backend/utils/adt/numeric.c
src/include/catalog/catversion.h
src/include/catalog/pg_amop.h
src/include/catalog/pg_amproc.h
src/include/catalog/pg_opclass.h
src/include/catalog/pg_operator.h
src/include/catalog/pg_opfamily.h
src/include/catalog/pg_proc.h
src/include/utils/builtins.h

index f6fe9a777e42d7ea31fe161665a3b4537d4bec9a..f720a453e54cbc2124a3e62746ed4ce23b14b7fd 100644 (file)
@@ -26,6 +26,7 @@
 #include <limits.h>
 #include <math.h>
 
+#include "access/hash.h"
 #include "catalog/pg_type.h"
 #include "libpq/pqformat.h"
 #include "utils/array.h"
@@ -1149,6 +1150,81 @@ cmp_numerics(Numeric num1, Numeric num2)
        return result;
 }
 
+Datum
+hash_numeric(PG_FUNCTION_ARGS)
+{
+       Numeric         key = PG_GETARG_NUMERIC(0);
+       Datum           digit_hash;
+       Datum           result;
+       int             weight;
+       int             start_offset;
+       int             end_offset;
+       int             i;
+       int             hash_len;
+
+       /* If it's NaN, don't try to hash the rest of the fields */
+       if (NUMERIC_IS_NAN(key))
+               PG_RETURN_UINT32(0);
+
+       weight           = key->n_weight;
+       start_offset = 0;
+       end_offset       = 0;
+
+       /*
+        * Omit any leading or trailing zeros from the input to the
+        * hash. The numeric implementation *should* guarantee that
+        * leading and trailing zeros are suppressed, but we're
+        * paranoid. Note that we measure the starting and ending offsets
+        * in units of NumericDigits, not bytes.
+        */
+       for (i = 0; i < NUMERIC_NDIGITS(key); i++)
+       {
+               if (NUMERIC_DIGITS(key)[i] != (NumericDigit) 0)
+                       break;
+
+               start_offset++;
+               /*
+                * The weight is effectively the # of digits before the
+                * decimal point, so decrement it for each leading zero we
+                * skip.
+                */
+               weight--;
+       }
+
+       /*
+        * If there are no non-zero digits, then the value of the number
+        * is zero, regardless of any other fields.
+        */
+       if (NUMERIC_NDIGITS(key) == start_offset)
+               PG_RETURN_UINT32(-1);
+
+       for (i = NUMERIC_NDIGITS(key) - 1; i >= 0; i--)
+       {
+               if (NUMERIC_DIGITS(key)[i] != (NumericDigit) 0)
+                       break;
+
+               end_offset++;
+       }
+
+       /* If we get here, there should be at least one non-zero digit */
+       Assert(start_offset + end_offset < NUMERIC_NDIGITS(key));
+
+       /*
+        * Note that we don't hash on the Numeric's scale, since two
+        * numerics can compare equal but have different scales. We also
+        * don't hash on the sign, although we could: since a sign
+        * difference implies inequality, this shouldn't affect correctness.
+        */
+       hash_len = NUMERIC_NDIGITS(key) - start_offset - end_offset;
+       digit_hash = hash_any((unsigned char *) (NUMERIC_DIGITS(key) + start_offset),
+                          hash_len * sizeof(NumericDigit));
+
+       /* Mix in the weight, via XOR */
+       result = digit_hash ^ weight;
+
+       PG_RETURN_DATUM(result);
+}
+
 
 /* ----------------------------------------------------------------------
  *
index c59f8762c0d94e449336a99cad89ed2dc365d016..f4ec3e34a880a6b71958c27c318bab92e4e73d89 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     200704151
+#define CATALOG_VERSION_NO     200705081
 
 #endif
index fb8b377f6d3324dab7c3c7c203e91720d76ecd02..c736d07838b24e7871801a2b92661c3cf6efcd85 100644 (file)
@@ -568,6 +568,8 @@ DATA(insert (       2232   19 19 1 f 2334   405 ));
 DATA(insert (  2235   1033 1033 1 f  974       405 ));
 /* uuid_ops */ 
 DATA(insert (  2969   2950 2950 1 f 2972 405 ));
+/* numeric_ops */
+DATA(insert (  1998   1700 1700 1 f 1752 405 ));
 
 
 /*
index e021822dad956057836df890d46dcc3defa5c1b7..2320d80d7d52150caaaf35639e52ec64587c30a7 100644 (file)
@@ -148,6 +148,7 @@ DATA(insert (       1990   26 26 1 453 ));
 DATA(insert (  1992   30 30 1 457 ));
 DATA(insert (  1995   25 25 1 400 ));
 DATA(insert (  1997   1083 1083 1 452 ));
+DATA(insert (  1998   1700 1700 1 432 ));
 DATA(insert (  1999   1184 1184 1 452 ));
 DATA(insert (  2001   1266 1266 1 1696 ));
 DATA(insert (  2040   1114 1114 1 452 ));
index 5f8337fe8872ee742e5a51e749a8d3b038dbaa41..71230129944a0936d415f14fd2844ddeb87a6f2c 100644 (file)
@@ -129,6 +129,7 @@ DATA(insert (       405             macaddr_ops                     PGNSP PGUID 1985  829 t 0 ));
 DATA(insert (  403             name_ops                        PGNSP PGUID 1986   19 t 0 ));
 DATA(insert (  405             name_ops                        PGNSP PGUID 1987   19 t 0 ));
 DATA(insert (  403             numeric_ops                     PGNSP PGUID 1988 1700 t 0 ));
+DATA(insert (  405             numeric_ops                     PGNSP PGUID 1998 1700 t 0 ));
 DATA(insert OID = 1981 ( 403   oid_ops         PGNSP PGUID 1989   26 t 0 ));
 #define OID_BTREE_OPS_OID 1981
 DATA(insert (  405             oid_ops                         PGNSP PGUID 1990   26 t 0 ));
index ee61d4c89db65fd3da2a72a3274aa5b642959dbe..4c4b80e8becabce7887273d0e395619064dc1181 100644 (file)
@@ -675,7 +675,7 @@ DATA(insert OID = 1630 (  "!~~*"  PGNSP PGUID b f f  1042 25        16 0 1629 bpcharicn
 
 /* NUMERIC type - OID's 1700-1799 */
 DATA(insert OID = 1751 (  "-"     PGNSP PGUID l f f    0 1700 1700    0        0 numeric_uminus - - ));
-DATA(insert OID = 1752 (  "="     PGNSP PGUID b t f 1700 1700   16 1752 1753 numeric_eq eqsel eqjoinsel ));
+DATA(insert OID = 1752 (  "="     PGNSP PGUID b t t 1700 1700   16 1752 1753 numeric_eq eqsel eqjoinsel ));
 DATA(insert OID = 1753 (  "<>"    PGNSP PGUID b f f 1700 1700   16 1753 1752 numeric_ne neqsel neqjoinsel ));
 DATA(insert OID = 1754 (  "<"     PGNSP PGUID b f f 1700 1700   16 1756 1757 numeric_lt scalarltsel scalarltjoinsel ));
 DATA(insert OID = 1755 (  "<="    PGNSP PGUID b f f 1700 1700   16 1757 1756 numeric_le scalarltsel scalarltjoinsel ));
index 0c139c3250ffe009c95fe8aca397fc9b7e530c6d..398a4fdff4830f93f7b81ed47c98d1e992f4d86c 100644 (file)
@@ -93,6 +93,7 @@ DATA(insert OID = 1986 (      403             name_ops                PGNSP PGUID ));
 #define NAME_BTREE_FAM_OID 1986
 DATA(insert OID = 1987 (       405             name_ops                PGNSP PGUID ));
 DATA(insert OID = 1988 (       403             numeric_ops             PGNSP PGUID ));
+DATA(insert OID = 1998 (       405             numeric_ops             PGNSP PGUID ));
 DATA(insert OID = 1989 (       403             oid_ops                 PGNSP PGUID ));
 #define OID_BTREE_FAM_OID 1989
 DATA(insert OID = 1990 (       405             oid_ops                 PGNSP PGUID ));
index 33dbec32f80ae4e40455aa5dd7938f6cb765c539..3b1ffe9edba752f00e537861987a9d5adfef204b 100644 (file)
@@ -838,6 +838,8 @@ DATA(insert OID = 399 (  hashmacaddr           PGNSP PGUID 12 1 0 f f t f i 1 23 "829"
 DESCR("hash");
 DATA(insert OID = 422 (  hashinet                 PGNSP PGUID 12 1 0 f f t f i 1 23 "869" _null_ _null_ _null_ hashinet - _null_ ));
 DESCR("hash");
+DATA(insert OID = 432 (  hash_numeric     PGNSP PGUID 12 1 0 f f t f i 1 23 "1700" _null_ _null_ _null_ hash_numeric - _null_ ));
+DESCR("hash");
 DATA(insert OID = 458 (  text_larger      PGNSP PGUID 12 1 0 f f t f i 2 25 "25 25" _null_ _null_ _null_ text_larger - _null_ ));
 DESCR("larger of two");
 DATA(insert OID = 459 (  text_smaller     PGNSP PGUID 12 1 0 f f t f i 2 25 "25 25" _null_ _null_ _null_ text_smaller - _null_ ));
index 9554179337c22a2b5d8f62810b903f07fc57eede..1e3fd6952708b87c232090edf95cf7c58cb8a1d5 100644 (file)
@@ -883,6 +883,7 @@ extern Datum int2_avg_accum(PG_FUNCTION_ARGS);
 extern Datum int4_avg_accum(PG_FUNCTION_ARGS);
 extern Datum int8_avg(PG_FUNCTION_ARGS);
 extern Datum width_bucket_numeric(PG_FUNCTION_ARGS);
+extern Datum hash_numeric(PG_FUNCTION_ARGS);
 
 /* ri_triggers.c */
 extern Datum RI_FKey_check_ins(PG_FUNCTION_ARGS);